mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-25 01:54:28 +08:00
* feat: pipeline page (#2168) * feat: Added POC of drag row table * fix: resolved eslint issue * fix: resolved webpack issue * fix: config changes * fix: removed unwanted code of antd table * feat: added icon on expand row * feat: ui of modal, alertbox & drag table * feat: added DraggableTableRow component * fix: issue on row reorder alert message * fix: styling & dynamic name when delete pipeline * feat: added edit modal ui * fix: modal on create or edit open issue * fix: types issue * fix: text change & styled component * fix: added react-i18next to translate constant * fix: removed webpack change * fix: webpack change * feat: added processor expand row poc * fix: linting issue * fix: sonar gate issues * fix: processor expand ui break issue * fix: added missing types * feat: added create & delete logic * fix: types issue * feat: added edit pipeline & processor logic * fix: added diff. local file for pipeline * fix: suggested changes for pipeline * fix: order of key name on useTranslation * test: added test cases * fix: code level changes * fix: code level changes * fix: edit tags issue * fix: changed inline function to handler * test: resolved test cases issue * fix: code level changes * fix: changed file structure * fix: added required styled component * feat: added common utils functions * fix: code level changes * test: added test cases * test: added more test cases * fix: abstracted code of pipeline column * fix: added utils for DraggableTableRow * fix: issue on drag at DraggableTableRow * test: added more test case * fix: abstracted code of processor column * fix: removed playwrite test * fix: abstracted code render method * fix: text correction pipline -> pipeline * test: added more test cases * fix: add pipeline form restructure * fix: add processor form restructure * fix: processor type issue * fix: forms abstraction * fix: on finish form abstraction * test: additional test cases of utils * feat: added new ui as per save config * fix: test cases issue * feat: added redux for data set managment * fix: updated logic of redux * fix: removed unused code * fix: modified pipeline data onchangeof processor data * fix: removed redux from pipeline * fix: test cases prop issue resolved * fix: reset field on add data * fix: sonar gate code smell * fix: sonar gate code duplicated issue * fix: code level changes * fix: add processor issue * fix: code level changes * chore: some of the types are updated * fix: inline css into styled component * fix: jsx element & type * fix: username, email object issue * fix: username, email object issue * fix: types issues * fix: inline condition removed * fix: code level changes * feat: integrated listing of pipeline & processor api * feat: integrated post api of pipeline & processor poc * feat: integrated delete api of pipeline & processor * fix: create pipeline api payload issue * fix: updated jest test cases * fix: processor order id ui issue * fix: create pipeline issue on payload * fix: add processor payload issue resolved * fix: added missing field on add pipeline * fix: processor type selection issue * fix: test cases updated * fix: sonar gate failed issue * fix: removed inline function * fix: enable switch logic at pipeline & processor level * fix: retain removed from type list * fix: build issue on jest * fix: test cases updated * chore: config is updated * chore: test snapshot is updated * fix: test cases updated * chore: test snapshot is updated * chore: test snapshot is updated * fix: api & ui integration of change history tab * chore: webpack is updated * test: test is updated * chore: build is fixed * chore: react-dnd is downgraded * chore: process is added * chore: build is fixed * chore: react-dnd is updated * fix: suggested changes * fix: tab pane issue * fix: build issue * fix: code level changes * fix: code level changes * fix: added types in def file * fix: code level changes * fix: test cases updated * fix: error message notification * fix: after reorder pipeline expand is not working * fix: on add of processor added optional field * feat: added search pipeline feature * fix: sonar gate failed issue * fix: processor reorder issue * fix: processor reorder output property issue * feat: added json_parser processor * fix: scalable code of component of column * fix: processor reorder issue * fix: search pipeline issue * fix: creating a pipeline description is an optional field * fix: nitya's suggested changes * fix: test cases updated * fix: edit data pipeline & processor * fix: pipeline cancel issue * fix: edit processor wrong payload * fix: processor reorder issue at payload * fix: pipeline undefined handle * fix: pipeline no data case * fix: updated test case * fix: resolved pipeline undefined issue * fix: processor data case * feat: added submenu system for pipeline * fix: pipeline suggested changes * fix: updated test case * fix: pipeline suggested changes * fix: test cases updated * test: updated test cases * chore: build issue * fix: pipeline level changes * fix: pipeline page access issue * fix: resolved issue on add operator when pipeline is empty * test: jest test cases updated * chore: try signoz cloud link is updated (#2928) * fix: solve history page issue --------- Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Vishal Sharma <makeavish786@gmail.com> Co-authored-by: Pranay Prateek <pranay@signoz.io> * chore: merge conflicts is resolved * test: snaps are updated * fix: remove unused dependency on process^0.11.10 --------- Co-authored-by: Chintan Sudani <46838508+techchintan@users.noreply.github.com> Co-authored-by: Vishal Sharma <makeavish786@gmail.com> Co-authored-by: Pranay Prateek <pranay@signoz.io> Co-authored-by: Raj <rkssisodiya@gmail.com>
This commit is contained in:
parent
fafa6f9960
commit
2cdafa0564
@ -21,7 +21,9 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!(lodash-es)/)'],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
|
@ -69,6 +69,9 @@
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"papaparse": "5.4.1",
|
||||
"react": "18.2.0",
|
||||
"react-addons-update": "15.6.3",
|
||||
"react-dnd": "16.0.1",
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-drag-listview": "2.0.0",
|
||||
"react-force-graph": "^1.41.0",
|
||||
@ -134,6 +137,7 @@
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/papaparse": "5.3.7",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-addons-update": "0.14.21",
|
||||
"@types/react-dom": "18.0.10",
|
||||
"@types/react-grid-layout": "^1.1.2",
|
||||
"@types/react-redux": "^7.1.11",
|
||||
|
@ -5,5 +5,8 @@
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls"
|
||||
"external_metrics": "External Calls",
|
||||
"pipelines": "Pipelines",
|
||||
"archives": "Archives",
|
||||
"logs_to_metrics": "Logs To Metrics"
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
"routes": {
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"all_errors": "All Exceptions"
|
||||
"all_errors": "All Exceptions",
|
||||
"pipelines": "Pipelines"
|
||||
}
|
||||
}
|
||||
|
44
frontend/public/locales/en/pipeline.json
Normal file
44
frontend/public/locales/en/pipeline.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"delete": "Delete",
|
||||
"filter": "Filter",
|
||||
"update": "Update",
|
||||
"create": "Create",
|
||||
"reorder": "Reorder",
|
||||
"cancel": "Cancel",
|
||||
"reorder_pipeline": "Do you want to reorder pipeline?",
|
||||
"reorder_pipeline_description": "Logs are processed sequentially in processors and pipelines. Reordering it may change how data is processed by them.",
|
||||
"delete_pipeline": "Do you want to delete pipeline",
|
||||
"delete_pipeline_description": "Logs are processed sequentially in processors and pipelines. Deleting a pipeline may change content of data processed by other pipelines & processors",
|
||||
"add_new_pipeline": "Add a New Pipeline",
|
||||
"new_pipeline": "New Pipeline",
|
||||
"enter_edit_mode": "Enter Edit Mode",
|
||||
"save_configuration": "Save Configuration",
|
||||
"edit_pipeline": "Edit Pipeline",
|
||||
"create_pipeline": "Create New Pipeline",
|
||||
"add_new_processor": "Add Processor",
|
||||
"edit_processor": "Edit Processor",
|
||||
"create_processor": "Create New Processor",
|
||||
"processor_type": "Select Processor Type",
|
||||
"reorder_processor": "Do you want to reorder processor?",
|
||||
"reorder_processor_description": "Logs are processed sequentially in processors. Reordering it may change how data is processed by them.",
|
||||
"delete_processor": "Do you want to delete processor",
|
||||
"delete_processor_description": "Logs are processed sequentially in processors. Deleting a processor may change content of data processed by other processors",
|
||||
"search_pipeline_placeholder": "Filter Pipelines",
|
||||
"pipeline_name_placeholder": "Name",
|
||||
"pipeline_tags_placeholder": "Tags",
|
||||
"pipeline_description_placeholder": "Enter description for your pipeline",
|
||||
"processor_name_placeholder": "Name",
|
||||
"processor_regex_placeholder": "Regex",
|
||||
"processor_parsefrom_placeholder": "Parse From",
|
||||
"processor_parseto_placeholder": "Parse From",
|
||||
"processor_onerror_placeholder": "on Error",
|
||||
"processor_pattern_placeholder": "Pattern",
|
||||
"processor_field_placeholder": "Field",
|
||||
"processor_value_placeholder": "Value",
|
||||
"processor_description_placeholder": "example rule: %{word:first}",
|
||||
"processor_trace_id_placeholder": "Trace Id Parce From",
|
||||
"processor_span_id_placeholder": "Span id Parse From",
|
||||
"processor_trace_flags_placeholder": "Trace flags parse from",
|
||||
"processor_from_placeholder": "From",
|
||||
"processor_to_placeholder": "To"
|
||||
}
|
@ -5,5 +5,8 @@
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls"
|
||||
"external_metrics": "External Calls",
|
||||
"pipelines": "Pipelines",
|
||||
"archives": "Archives",
|
||||
"logs_to_metrics": "Logs To Metrics"
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
"routes": {
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"all_errors": "All Exceptions"
|
||||
"all_errors": "All Exceptions",
|
||||
"pipelines": "Pipelines"
|
||||
}
|
||||
}
|
||||
|
@ -132,3 +132,7 @@ export const SomethingWentWrong = Loadable(
|
||||
export const LicensePage = Loadable(
|
||||
() => import(/* webpackChunkName: "All Channels" */ 'pages/License'),
|
||||
);
|
||||
|
||||
export const PipelinePage = Loadable(
|
||||
() => import(/* webpackChunkName: "Pipelines" */ 'pages/Pipelines'),
|
||||
);
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
NewDashboardPage,
|
||||
OrganizationSettings,
|
||||
PasswordReset,
|
||||
PipelinePage,
|
||||
ServiceMapPage,
|
||||
ServiceMetricsPage,
|
||||
ServicesTablePage,
|
||||
@ -253,6 +254,13 @@ const routes: AppRoutes[] = [
|
||||
key: 'SOMETHING_WENT_WRONG',
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
path: ROUTES.PIPELINES,
|
||||
exact: true,
|
||||
component: PipelinePage,
|
||||
key: 'PIPELINES',
|
||||
isPrivate: true,
|
||||
},
|
||||
];
|
||||
|
||||
export interface AppRoutes {
|
||||
|
25
frontend/src/api/pipeline/get.ts
Normal file
25
frontend/src/api/pipeline/get.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Pipeline } from 'types/api/pipeline/def';
|
||||
import { Props } from 'types/api/pipeline/get';
|
||||
|
||||
const get = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<Pipeline> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/logs/pipelines/${props.version}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response?.data?.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default get;
|
25
frontend/src/api/pipeline/post.ts
Normal file
25
frontend/src/api/pipeline/post.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Pipeline } from 'types/api/pipeline/def';
|
||||
import { Props } from 'types/api/pipeline/post';
|
||||
|
||||
const post = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<Pipeline> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/logs/pipelines', props.data);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default post;
|
54
frontend/src/components/DraggableTableRow/index.tsx
Normal file
54
frontend/src/components/DraggableTableRow/index.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
|
||||
import { dragHandler, dropHandler } from './utils';
|
||||
|
||||
const type = 'DraggableTableRow';
|
||||
|
||||
function DraggableTableRow({
|
||||
index,
|
||||
moveRow,
|
||||
className,
|
||||
style,
|
||||
...restProps
|
||||
}: DraggableTableRowProps): JSX.Element {
|
||||
const ref = useRef<HTMLTableRowElement>(null);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(item: { index: number }) => {
|
||||
if (moveRow) moveRow(item.index, index);
|
||||
},
|
||||
[moveRow, index],
|
||||
);
|
||||
|
||||
const [, drop] = useDrop({
|
||||
accept: type,
|
||||
collect: dropHandler,
|
||||
drop: handleDrop,
|
||||
});
|
||||
|
||||
const [, drag] = useDrag({
|
||||
type,
|
||||
item: { index },
|
||||
collect: dragHandler,
|
||||
});
|
||||
drop(drag(ref));
|
||||
|
||||
return (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={className}
|
||||
style={{ ...style }}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface DraggableTableRowProps
|
||||
extends React.HTMLAttributes<HTMLTableRowElement> {
|
||||
index: number;
|
||||
moveRow: (dragIndex: number, hoverIndex: number) => void;
|
||||
}
|
||||
|
||||
export default DraggableTableRow;
|
@ -0,0 +1,38 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { Table } from 'antd';
|
||||
import { matchMedia } from 'container/PipelinePage/tests/AddNewPipeline.test';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import DraggableTableRow from '..';
|
||||
|
||||
beforeAll(() => {
|
||||
matchMedia();
|
||||
});
|
||||
|
||||
jest.mock('react-dnd', () => ({
|
||||
useDrop: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
useDrag: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
}));
|
||||
|
||||
describe('DraggableTableRow Snapshot test', () => {
|
||||
it('should render DraggableTableRow', async () => {
|
||||
const { asFragment } = render(
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Table
|
||||
components={{
|
||||
body: {
|
||||
row: DraggableTableRow,
|
||||
},
|
||||
}}
|
||||
pagination={false}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,103 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DraggableTableRow Snapshot test should render DraggableTableRow 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="ant-table-wrapper css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<div
|
||||
class="ant-spin-nested-loading css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<div
|
||||
class="ant-spin-container"
|
||||
>
|
||||
<div
|
||||
class="ant-table ant-table-empty"
|
||||
>
|
||||
<div
|
||||
class="ant-table-container"
|
||||
>
|
||||
<div
|
||||
class="ant-table-content"
|
||||
>
|
||||
<table
|
||||
style="table-layout: auto;"
|
||||
>
|
||||
<colgroup />
|
||||
<thead
|
||||
class="ant-table-thead"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
class="ant-table-cell"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="ant-table-tbody"
|
||||
>
|
||||
<tr
|
||||
class="ant-table-placeholder"
|
||||
>
|
||||
<td
|
||||
class="ant-table-cell"
|
||||
>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-1i536d8 ant-empty ant-empty-normal"
|
||||
>
|
||||
<div
|
||||
class="ant-empty-image"
|
||||
>
|
||||
<svg
|
||||
height="41"
|
||||
viewBox="0 0 64 41"
|
||||
width="64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
transform="translate(0 1)"
|
||||
>
|
||||
<ellipse
|
||||
cx="32"
|
||||
cy="33"
|
||||
fill="#f5f5f5"
|
||||
rx="32"
|
||||
ry="7"
|
||||
/>
|
||||
<g
|
||||
fill-rule="nonzero"
|
||||
stroke="#d9d9d9"
|
||||
>
|
||||
<path
|
||||
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
|
||||
/>
|
||||
<path
|
||||
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="ant-empty-description"
|
||||
>
|
||||
No data
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PipelinePage container test should render AddNewPipeline section 1`] = `<DocumentFragment />`;
|
@ -0,0 +1,44 @@
|
||||
import { dragHandler, dropHandler } from '../utils';
|
||||
|
||||
jest.mock('react-dnd', () => ({
|
||||
useDrop: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
useDrag: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
|
||||
}));
|
||||
|
||||
describe('Utils testing of DraggableTableRow component', () => {
|
||||
test('Should dropHandler return true', () => {
|
||||
const monitor = {
|
||||
isOver: jest.fn().mockReturnValueOnce(true),
|
||||
} as never;
|
||||
const dropDataTruthy = dropHandler(monitor);
|
||||
|
||||
expect(dropDataTruthy).toEqual({ isOver: true });
|
||||
});
|
||||
|
||||
test('Should dropHandler return false', () => {
|
||||
const monitor = {
|
||||
isOver: jest.fn().mockReturnValueOnce(false),
|
||||
} as never;
|
||||
const dropDataFalsy = dropHandler(monitor);
|
||||
|
||||
expect(dropDataFalsy).toEqual({ isOver: false });
|
||||
});
|
||||
|
||||
test('Should dragHandler return true', () => {
|
||||
const monitor = {
|
||||
isDragging: jest.fn().mockReturnValueOnce(true),
|
||||
} as never;
|
||||
const dragDataTruthy = dragHandler(monitor);
|
||||
|
||||
expect(dragDataTruthy).toEqual({ isDragging: true });
|
||||
});
|
||||
|
||||
test('Should dragHandler return false', () => {
|
||||
const monitor = {
|
||||
isDragging: jest.fn().mockReturnValueOnce(false),
|
||||
} as never;
|
||||
const dragDataFalsy = dragHandler(monitor);
|
||||
|
||||
expect(dragDataFalsy).toEqual({ isDragging: false });
|
||||
});
|
||||
});
|
15
frontend/src/components/DraggableTableRow/utils.ts
Normal file
15
frontend/src/components/DraggableTableRow/utils.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { DragSourceMonitor, DropTargetMonitor } from 'react-dnd';
|
||||
|
||||
export function dropHandler(monitor: DropTargetMonitor): { isOver: boolean } {
|
||||
return {
|
||||
isOver: monitor.isOver(),
|
||||
};
|
||||
}
|
||||
|
||||
export function dragHandler(
|
||||
monitor: DragSourceMonitor,
|
||||
): { isDragging: boolean } {
|
||||
return {
|
||||
isDragging: monitor.isDragging(),
|
||||
};
|
||||
}
|
@ -32,6 +32,8 @@ const ROUTES = {
|
||||
HOME_PAGE: '/',
|
||||
PASSWORD_RESET: '/password-reset',
|
||||
LIST_LICENSES: '/licenses',
|
||||
TRACE_EXPLORER: '/trace-explorer',
|
||||
PIPELINES: '/pipelines',
|
||||
};
|
||||
|
||||
export default ROUTES;
|
||||
|
@ -44,6 +44,12 @@ const themeColors = {
|
||||
lightWhite: '#ffffffd9',
|
||||
borderLightGrey: '#d9d9d9',
|
||||
borderDarkGrey: '#424242',
|
||||
gainsboro: '#DBDBDB',
|
||||
navyBlue: '#1668DC',
|
||||
lightSkyBlue: '#8DCFF8',
|
||||
neroBlack: '#1d1d1d',
|
||||
snowWhite: '#fafafa',
|
||||
gamboge: '#D89614',
|
||||
bckgGrey: '#1d1d1d',
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
import { IconDataSpan } from 'container/PipelinePage/styles';
|
||||
|
||||
import { getDeploymentStage, getDeploymentStageIcon } from './utils';
|
||||
|
||||
function DeploymentStage(deployStatus: string): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{getDeploymentStageIcon(deployStatus)}
|
||||
<IconDataSpan>{getDeploymentStage(deployStatus)}</IconDataSpan>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DeploymentStage;
|
@ -0,0 +1,9 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
function DeploymentTime(deployTime: string): JSX.Element {
|
||||
return (
|
||||
<span>{dayjs(deployTime).locale('en').format('MMMM DD, YYYY hh:mm A')}</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default DeploymentTime;
|
@ -0,0 +1,24 @@
|
||||
import { Table } from 'antd';
|
||||
import { Pipeline } from 'types/api/pipeline/def';
|
||||
|
||||
import { changeHistoryColumns } from '../../PipelineListsView/config';
|
||||
import { HistoryTableWrapper } from '../../styles';
|
||||
import { historyPagination } from '../config';
|
||||
|
||||
function ChangeHistory({ piplineData }: ChangeHistoryProps): JSX.Element {
|
||||
return (
|
||||
<HistoryTableWrapper>
|
||||
<Table
|
||||
columns={changeHistoryColumns}
|
||||
dataSource={piplineData?.history ?? []}
|
||||
pagination={historyPagination}
|
||||
/>
|
||||
</HistoryTableWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
interface ChangeHistoryProps {
|
||||
piplineData: Pipeline;
|
||||
}
|
||||
|
||||
export default ChangeHistory;
|
@ -0,0 +1,39 @@
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
CloseCircleFilled,
|
||||
ExclamationCircleFilled,
|
||||
LoadingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Spin } from 'antd';
|
||||
|
||||
export function getDeploymentStage(value: string): string {
|
||||
switch (value) {
|
||||
case 'IN_PROGRESS':
|
||||
return 'In Progress';
|
||||
case 'DEPLOYED':
|
||||
return 'Deployed';
|
||||
case 'DIRTY':
|
||||
return 'Dirty';
|
||||
case 'FAILED':
|
||||
return 'Failed';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function getDeploymentStageIcon(value: string): JSX.Element {
|
||||
switch (value) {
|
||||
case 'IN_PROGRESS':
|
||||
return (
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 15 }} spin />} />
|
||||
);
|
||||
case 'DEPLOYED':
|
||||
return <CheckCircleFilled />;
|
||||
case 'DIRTY':
|
||||
return <ExclamationCircleFilled />;
|
||||
case 'FAILED':
|
||||
return <CloseCircleFilled />;
|
||||
default:
|
||||
return <span />;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { EditFilled, PlusOutlined } from '@ant-design/icons';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { useCallback, 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,
|
||||
piplineData,
|
||||
}: CreatePipelineButtonProps): JSX.Element {
|
||||
const { t } = useTranslation(['pipeline']);
|
||||
|
||||
const isAddNewPipelineVisible = useMemo(
|
||||
() => checkDataLength(piplineData?.pipelines),
|
||||
[piplineData?.pipelines],
|
||||
);
|
||||
const isDisabled = isActionMode === ActionMode.Editing;
|
||||
|
||||
const actionHandler = useCallback(
|
||||
(action: string, setStateFunc: (action: string) => void) => (): void =>
|
||||
setStateFunc(action),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<ButtonContainer>
|
||||
<TextToolTip text={t('add_new_pipeline')} />
|
||||
{isAddNewPipelineVisible && (
|
||||
<CustomButton
|
||||
icon={<EditFilled />}
|
||||
onClick={actionHandler(ActionMode.Editing, setActionMode)}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{t('enter_edit_mode')}
|
||||
</CustomButton>
|
||||
)}
|
||||
{!isAddNewPipelineVisible && (
|
||||
<CustomButton
|
||||
icon={<PlusOutlined />}
|
||||
onClick={actionHandler(ActionType.AddPipeline, setActionType)}
|
||||
type="primary"
|
||||
>
|
||||
{t('new_pipeline')}
|
||||
</CustomButton>
|
||||
)}
|
||||
</ButtonContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface CreatePipelineButtonProps {
|
||||
setActionType: (actionType: string) => void;
|
||||
isActionMode: string;
|
||||
setActionMode: (actionMode: string) => void;
|
||||
piplineData: Pipeline;
|
||||
}
|
||||
|
||||
export default CreatePipelineButton;
|
@ -0,0 +1,30 @@
|
||||
import { Input } from 'antd';
|
||||
import React, { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function PipelinesSearchSection({
|
||||
setPipelineSearchValue,
|
||||
}: PipelinesSearchSectionProps): JSX.Element {
|
||||
const { t } = useTranslation(['pipeline']);
|
||||
|
||||
const onSeachHandler = useCallback(
|
||||
(event: React.SetStateAction<string>) => {
|
||||
setPipelineSearchValue(event);
|
||||
},
|
||||
[setPipelineSearchValue],
|
||||
);
|
||||
|
||||
return (
|
||||
<Input.Search
|
||||
allowClear
|
||||
placeholder={t('search_pipeline_placeholder')}
|
||||
onSearch={onSeachHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface PipelinesSearchSectionProps {
|
||||
setPipelineSearchValue: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export default PipelinesSearchSection;
|
@ -0,0 +1,43 @@
|
||||
import { useState } from 'react';
|
||||
import { Pipeline } from 'types/api/pipeline/def';
|
||||
|
||||
import PipelineListsView from '../../PipelineListsView';
|
||||
import CreatePipelineButton from './CreatePipelineButton';
|
||||
import PipelinesSearchSection from './PipelinesSearchSection';
|
||||
|
||||
function PipelinePageLayout({
|
||||
refetchPipelineLists,
|
||||
piplineData,
|
||||
}: PipelinePageLayoutProps): JSX.Element {
|
||||
const [isActionType, setActionType] = useState<string>();
|
||||
const [isActionMode, setActionMode] = useState<string>('viewing-mode');
|
||||
const [pipelineSearchValue, setPipelineSearchValue] = useState<string>('');
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreatePipelineButton
|
||||
setActionType={setActionType}
|
||||
setActionMode={setActionMode}
|
||||
isActionMode={isActionMode}
|
||||
piplineData={piplineData}
|
||||
/>
|
||||
<PipelinesSearchSection setPipelineSearchValue={setPipelineSearchValue} />
|
||||
<PipelineListsView
|
||||
isActionType={String(isActionType)}
|
||||
setActionType={setActionType}
|
||||
setActionMode={setActionMode}
|
||||
isActionMode={isActionMode}
|
||||
piplineData={piplineData}
|
||||
refetchPipelineLists={refetchPipelineLists}
|
||||
pipelineSearchValue={pipelineSearchValue}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface PipelinePageLayoutProps {
|
||||
refetchPipelineLists: VoidFunction;
|
||||
piplineData: Pipeline;
|
||||
}
|
||||
|
||||
export default PipelinePageLayout;
|
3
frontend/src/container/PipelinePage/Layouts/config.ts
Normal file
3
frontend/src/container/PipelinePage/Layouts/config.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const historyPagination = {
|
||||
defaultPageSize: 5,
|
||||
};
|
4
frontend/src/container/PipelinePage/Layouts/utils.ts
Normal file
4
frontend/src/container/PipelinePage/Layouts/utils.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { PipelineData } from 'types/api/pipeline/def';
|
||||
|
||||
export const checkDataLength = (data: Array<PipelineData>): boolean =>
|
||||
data?.length > 0;
|
@ -0,0 +1,31 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ProcessorFormField } from '../../AddNewProcessor/config';
|
||||
import { FormLabelStyle } from '../styles';
|
||||
|
||||
function DescriptionTextArea({
|
||||
fieldData,
|
||||
}: DescriptionTextAreaProps): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
required={false}
|
||||
name={fieldData.name}
|
||||
label={<FormLabelStyle>{fieldData.fieldName}</FormLabelStyle>}
|
||||
key={fieldData.id}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
name={fieldData.name}
|
||||
placeholder={t(fieldData.placeholder)}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
interface DescriptionTextAreaProps {
|
||||
fieldData: ProcessorFormField;
|
||||
}
|
||||
export default DescriptionTextArea;
|
@ -0,0 +1,31 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ProcessorFormField } from '../../AddNewProcessor/config';
|
||||
import { formValidationRules } from '../../config';
|
||||
import { FormLabelStyle } from '../styles';
|
||||
|
||||
function FilterSearch({ fieldData }: FilterSearchProps): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
required={false}
|
||||
label={<FormLabelStyle>{fieldData.fieldName}</FormLabelStyle>}
|
||||
key={fieldData.id}
|
||||
rules={formValidationRules}
|
||||
name={fieldData.name}
|
||||
>
|
||||
<Input.Search
|
||||
id={fieldData.id.toString()}
|
||||
name={fieldData.name}
|
||||
placeholder={t(fieldData.placeholder)}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
interface FilterSearchProps {
|
||||
fieldData: ProcessorFormField;
|
||||
}
|
||||
export default FilterSearch;
|
@ -0,0 +1,27 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ProcessorFormField } from '../../AddNewProcessor/config';
|
||||
import { formValidationRules } from '../../config';
|
||||
import { FormLabelStyle } from '../styles';
|
||||
|
||||
function NameInput({ fieldData }: NameInputProps): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
required={false}
|
||||
label={<FormLabelStyle>{fieldData.fieldName}</FormLabelStyle>}
|
||||
key={fieldData.id}
|
||||
rules={formValidationRules}
|
||||
name={fieldData.name}
|
||||
>
|
||||
<Input name={fieldData.name} placeholder={t(fieldData.placeholder)} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
interface NameInputProps {
|
||||
fieldData: ProcessorFormField;
|
||||
}
|
||||
export default NameInput;
|
@ -0,0 +1,36 @@
|
||||
import { Form } from 'antd';
|
||||
import TagInput from 'container/PipelinePage/components/TagInput';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ProcessorFormField } from '../../AddNewProcessor/config';
|
||||
import { FormLabelStyle } from '../styles';
|
||||
|
||||
function ProcessorTags({
|
||||
fieldData,
|
||||
setTagsListData,
|
||||
tagsListData,
|
||||
}: ProcessorTagsProps): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
required={false}
|
||||
label={<FormLabelStyle>{fieldData.fieldName}</FormLabelStyle>}
|
||||
key={fieldData.id}
|
||||
name={fieldData.name}
|
||||
>
|
||||
<TagInput
|
||||
setTagsListData={setTagsListData}
|
||||
tagsListData={tagsListData}
|
||||
placeHolder={t(fieldData.placeholder)}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
interface ProcessorTagsProps {
|
||||
fieldData: ProcessorFormField;
|
||||
setTagsListData: (tags: Array<string>) => void;
|
||||
tagsListData: Array<string>;
|
||||
}
|
||||
export default ProcessorTags;
|
@ -0,0 +1,150 @@
|
||||
import { Button, Divider, Form, Modal } from 'antd';
|
||||
import React, { useCallback, 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 AppReducer from 'types/reducer/app';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ModalButtonWrapper, ModalTitle } from '../styles';
|
||||
import { getEditedDataSource, getRecordIndex } from '../utils';
|
||||
import { renderPipelineForm } from './utils';
|
||||
|
||||
function AddNewPipeline({
|
||||
isActionType,
|
||||
setActionType,
|
||||
selectedPipelineData,
|
||||
setShowSaveButton,
|
||||
setCurrPipelineData,
|
||||
currPipelineData,
|
||||
}: AddNewPipelineProps): JSX.Element {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation('pipeline');
|
||||
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const isEdit = isActionType === 'edit-pipeline';
|
||||
const isAdd = isActionType === 'add-pipeline';
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdit) {
|
||||
form.setFieldsValue(selectedPipelineData);
|
||||
}
|
||||
if (isAdd) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [form, isEdit, isAdd, selectedPipelineData]);
|
||||
|
||||
const onFinish = (values: PipelineData): void => {
|
||||
const newPipeLineData: PipelineData = {
|
||||
id: v4(),
|
||||
orderId: (currPipelineData?.length || 0) + 1,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdBy: user?.name || '',
|
||||
name: values.name,
|
||||
alias: values.name.replace(/\s/g, ''),
|
||||
description: values.description,
|
||||
filter: values.filter,
|
||||
config: [],
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
if (isEdit && selectedPipelineData) {
|
||||
const findRecordIndex = getRecordIndex(
|
||||
currPipelineData,
|
||||
selectedPipelineData,
|
||||
'id',
|
||||
);
|
||||
const updatedPipelineData: PipelineData = {
|
||||
...currPipelineData[findRecordIndex],
|
||||
...values,
|
||||
};
|
||||
|
||||
const editedPipelineData = getEditedDataSource(
|
||||
currPipelineData,
|
||||
selectedPipelineData,
|
||||
'id',
|
||||
updatedPipelineData,
|
||||
);
|
||||
|
||||
setCurrPipelineData(editedPipelineData);
|
||||
}
|
||||
if (isAdd) {
|
||||
setCurrPipelineData((prevState) => {
|
||||
if (prevState) return [...prevState, newPipeLineData];
|
||||
return [newPipeLineData];
|
||||
});
|
||||
}
|
||||
setActionType(undefined);
|
||||
};
|
||||
|
||||
const onCancelModalHandler = (): void => {
|
||||
setActionType(undefined);
|
||||
};
|
||||
|
||||
const modalTitle = useMemo(
|
||||
(): string =>
|
||||
isEdit
|
||||
? `${t('edit_pipeline')} : ${selectedPipelineData?.name}`
|
||||
: t('create_pipeline'),
|
||||
[isEdit, selectedPipelineData?.name, t],
|
||||
);
|
||||
|
||||
const onOkModalHandler = useCallback(
|
||||
() => setShowSaveButton(ActionMode.Editing),
|
||||
[setShowSaveButton],
|
||||
);
|
||||
|
||||
const isOpen = useMemo(() => isEdit || isAdd, [isAdd, isEdit]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<ModalTitle level={4}>{modalTitle}</ModalTitle>}
|
||||
centered
|
||||
open={isOpen}
|
||||
width={800}
|
||||
footer={null}
|
||||
onCancel={onCancelModalHandler}
|
||||
>
|
||||
<Divider plain />
|
||||
<Form
|
||||
name="add-new-pipeline"
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
>
|
||||
{renderPipelineForm()}
|
||||
<Divider plain />
|
||||
<Form.Item>
|
||||
<ModalButtonWrapper>
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
onClick={onOkModalHandler}
|
||||
>
|
||||
{isEdit ? t('update') : t('create')}
|
||||
</Button>
|
||||
<Button key="cancel" onClick={onCancelModalHandler}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</ModalButtonWrapper>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
interface AddNewPipelineProps {
|
||||
isActionType: string;
|
||||
setActionType: (actionType?: ActionType) => void;
|
||||
selectedPipelineData: PipelineData | undefined;
|
||||
setShowSaveButton: (actionMode: ActionMode) => void;
|
||||
setCurrPipelineData: (
|
||||
value: React.SetStateAction<Array<PipelineData>>,
|
||||
) => void;
|
||||
currPipelineData: Array<PipelineData>;
|
||||
}
|
||||
|
||||
export default AddNewPipeline;
|
@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FormLabelStyle = styled.span`
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem;
|
||||
`;
|
@ -0,0 +1,7 @@
|
||||
import { pipelineFields } from '../config';
|
||||
|
||||
export const renderPipelineForm = (): Array<JSX.Element> =>
|
||||
pipelineFields.map((field) => {
|
||||
const Component = field.component;
|
||||
return <Component key={field.id} fieldData={field} />;
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { ModalFooterTitle } from 'container/PipelinePage/styles';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { formValidationRules } from '../../config';
|
||||
import { ProcessorFormField } from '../config';
|
||||
import { Container, FormWrapper, PipelineIndexIcon } from '../styles';
|
||||
|
||||
function NameInput({ fieldData }: NameInputProps): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PipelineIndexIcon size="small">
|
||||
{Number(fieldData.id) + 1}
|
||||
</PipelineIndexIcon>
|
||||
<FormWrapper>
|
||||
<Form.Item
|
||||
required={false}
|
||||
label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>}
|
||||
key={fieldData.id}
|
||||
name={fieldData.name}
|
||||
initialValue={fieldData.initialValue}
|
||||
rules={fieldData.rules ? fieldData.rules : formValidationRules}
|
||||
>
|
||||
<Input placeholder={t(fieldData.placeholder)} name={fieldData.name} />
|
||||
</Form.Item>
|
||||
</FormWrapper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface NameInputProps {
|
||||
fieldData: ProcessorFormField;
|
||||
}
|
||||
export default NameInput;
|
@ -0,0 +1,37 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { ModalFooterTitle } from 'container/PipelinePage/styles';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ProcessorFormField } from '../config';
|
||||
import { Container, FormWrapper, PipelineIndexIcon } from '../styles';
|
||||
|
||||
function ParsingRulesTextArea({
|
||||
fieldData,
|
||||
}: ParsingRulesTextAreaProps): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<PipelineIndexIcon size="small">
|
||||
{Number(fieldData.id) + 1}
|
||||
</PipelineIndexIcon>
|
||||
<FormWrapper>
|
||||
<Form.Item
|
||||
name={fieldData.name}
|
||||
label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
name={fieldData.name}
|
||||
placeholder={t(fieldData.placeholder)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormWrapper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface ParsingRulesTextAreaProps {
|
||||
fieldData: ProcessorFormField;
|
||||
}
|
||||
export default ParsingRulesTextArea;
|
@ -0,0 +1,44 @@
|
||||
import { Select } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DEFAULT_PROCESSOR_TYPE, processorTypes } from '../config';
|
||||
import {
|
||||
PipelineIndexIcon,
|
||||
ProcessorType,
|
||||
ProcessorTypeContainer,
|
||||
ProcessorTypeWrapper,
|
||||
StyledSelect,
|
||||
} from '../styles';
|
||||
|
||||
function TypeSelect({ onChange, value }: TypeSelectProps): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<ProcessorTypeWrapper>
|
||||
<PipelineIndexIcon size="small">1</PipelineIndexIcon>
|
||||
<ProcessorTypeContainer>
|
||||
<ProcessorType>{t('processor_type')}</ProcessorType>
|
||||
<StyledSelect
|
||||
onChange={(value: string | unknown): void => onChange(value)}
|
||||
value={value}
|
||||
>
|
||||
{processorTypes.map(({ value, label }) => (
|
||||
<Select.Option key={value + label} value={value}>
|
||||
{label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</StyledSelect>
|
||||
</ProcessorTypeContainer>
|
||||
</ProcessorTypeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
TypeSelect.defaultProps = {
|
||||
value: DEFAULT_PROCESSOR_TYPE,
|
||||
};
|
||||
|
||||
interface TypeSelectProps {
|
||||
onChange: (value: string | unknown) => void;
|
||||
value?: string;
|
||||
}
|
||||
export default TypeSelect;
|
@ -0,0 +1,226 @@
|
||||
type ProcessorType = {
|
||||
key: string;
|
||||
value: string;
|
||||
label: string;
|
||||
title?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const processorTypes: Array<ProcessorType> = [
|
||||
{ key: 'grok_parser', value: 'grok_parser', label: 'Grok' },
|
||||
{ key: 'json_parser', value: 'json_parser', label: 'Json Parser' },
|
||||
{ key: 'regex_parser', value: 'regex_parser', label: 'Regex' },
|
||||
{ key: 'add', value: 'add', label: 'Add' },
|
||||
{ key: 'remove', value: 'remove', label: 'Remove' },
|
||||
{ key: 'trace_parser', value: 'trace_parser', label: 'Trace Parser' },
|
||||
// { key: 'retain', value: 'retain', label: 'Retain' }, @Chintan - Commented as per Nitya's suggestion
|
||||
{ key: 'move', value: 'move', label: 'Move' },
|
||||
{ key: 'copy', value: 'copy', label: 'Copy' },
|
||||
];
|
||||
|
||||
export const DEFAULT_PROCESSOR_TYPE = processorTypes[0].value;
|
||||
|
||||
export type ProcessorFormField = {
|
||||
id: number;
|
||||
fieldName: string;
|
||||
placeholder: string;
|
||||
name: string;
|
||||
rules?: Array<{ [key: string]: boolean }>;
|
||||
initialValue?: string;
|
||||
};
|
||||
|
||||
const commonFields = [
|
||||
{
|
||||
id: 3,
|
||||
fieldName: 'Parse From',
|
||||
placeholder: 'processor_parsefrom_placeholder',
|
||||
name: 'parse_from', // optional
|
||||
rules: [],
|
||||
initialValue: 'body',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
fieldName: 'Parse To',
|
||||
placeholder: 'processor_parseto_placeholder',
|
||||
name: 'parse_to', // optional
|
||||
rules: [],
|
||||
initialValue: 'attributes',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
fieldName: 'On Error',
|
||||
placeholder: 'processor_onerror_placeholder',
|
||||
name: 'on_error', // optional
|
||||
rules: [],
|
||||
initialValue: 'send',
|
||||
},
|
||||
];
|
||||
|
||||
export const processorFields: { [key: string]: Array<ProcessorFormField> } = {
|
||||
grok_parser: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Grok Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Pattern',
|
||||
placeholder: 'processor_pattern_placeholder',
|
||||
name: 'pattern',
|
||||
},
|
||||
...commonFields,
|
||||
],
|
||||
json_parser: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Json Parser Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Parse From',
|
||||
placeholder: 'processor_parsefrom_placeholder',
|
||||
name: 'parse_from',
|
||||
initialValue: 'body',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fieldName: 'Parse To',
|
||||
placeholder: 'processor_parseto_placeholder',
|
||||
name: 'parse_to',
|
||||
initialValue: 'attributes',
|
||||
},
|
||||
],
|
||||
regex_parser: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Regex Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Define Regex',
|
||||
placeholder: 'processor_regex_placeholder',
|
||||
name: 'regex',
|
||||
},
|
||||
...commonFields,
|
||||
],
|
||||
add: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Add Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Field',
|
||||
placeholder: 'processor_field_placeholder',
|
||||
name: 'field',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fieldName: 'Value',
|
||||
placeholder: 'processor_value_placeholder',
|
||||
name: 'value',
|
||||
},
|
||||
],
|
||||
remove: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Remove Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Field',
|
||||
placeholder: 'processor_field_placeholder',
|
||||
name: 'field',
|
||||
},
|
||||
],
|
||||
trace_parser: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Trace Parser Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Trace Id Parce From',
|
||||
placeholder: 'processor_trace_id_placeholder',
|
||||
name: 'traceId',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fieldName: 'Span id Parse From',
|
||||
placeholder: 'processor_span_id_placeholder',
|
||||
name: 'spanId',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
fieldName: 'Trace flags parse from',
|
||||
placeholder: 'processor_trace_flags_placeholder',
|
||||
name: 'traceFlags',
|
||||
},
|
||||
],
|
||||
retain: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Retain Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Fields',
|
||||
placeholder: 'processor_fields_placeholder',
|
||||
name: 'fields',
|
||||
},
|
||||
],
|
||||
move: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Move Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'From',
|
||||
placeholder: 'processor_from_placeholder',
|
||||
name: 'from',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fieldName: 'To',
|
||||
placeholder: 'processor_to_placeholder',
|
||||
name: 'to',
|
||||
},
|
||||
],
|
||||
copy: [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Name of Copy Processor',
|
||||
placeholder: 'processor_name_placeholder',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'From',
|
||||
placeholder: 'processor_from_placeholder',
|
||||
name: 'from',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fieldName: 'To',
|
||||
placeholder: 'processor_to_placeholder',
|
||||
name: 'to',
|
||||
},
|
||||
],
|
||||
};
|
@ -0,0 +1,195 @@
|
||||
import { Button, Divider, Form, Modal } from 'antd';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActionMode,
|
||||
ActionType,
|
||||
PipelineData,
|
||||
ProcessorData,
|
||||
} from 'types/api/pipeline/def';
|
||||
|
||||
import { ModalButtonWrapper, ModalTitle } from '../styles';
|
||||
import { getEditedDataSource, getRecordIndex } from '../utils';
|
||||
import { DEFAULT_PROCESSOR_TYPE } from './config';
|
||||
import TypeSelect from './FormFields/TypeSelect';
|
||||
import { renderProcessorForm } from './utils';
|
||||
|
||||
function AddNewProcessor({
|
||||
isActionType,
|
||||
setActionType,
|
||||
selectedProcessorData,
|
||||
setShowSaveButton,
|
||||
expandedPipelineData,
|
||||
setExpandedPipelineData,
|
||||
}: AddNewProcessorProps): JSX.Element {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation('pipeline');
|
||||
const [processorType, setProcessorType] = useState<string>(
|
||||
DEFAULT_PROCESSOR_TYPE,
|
||||
);
|
||||
|
||||
const isEdit = isActionType === 'edit-processor';
|
||||
const isAdd = isActionType === 'add-processor';
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdit && selectedProcessorData && expandedPipelineData?.config) {
|
||||
const findRecordIndex = getRecordIndex(
|
||||
expandedPipelineData?.config,
|
||||
selectedProcessorData,
|
||||
'id',
|
||||
);
|
||||
|
||||
const updatedProcessorData = {
|
||||
...expandedPipelineData?.config?.[findRecordIndex],
|
||||
};
|
||||
setProcessorType(updatedProcessorData.type);
|
||||
form.setFieldsValue(updatedProcessorData);
|
||||
}
|
||||
if (isAdd) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [form, isEdit, isAdd, selectedProcessorData, expandedPipelineData?.config]);
|
||||
|
||||
const handleProcessorType = (value: string | unknown): void => {
|
||||
const typedValue = String(value) || DEFAULT_PROCESSOR_TYPE;
|
||||
setProcessorType(typedValue);
|
||||
};
|
||||
|
||||
const onFinish = (values: { name: string }): void => {
|
||||
const totalDataLength = expandedPipelineData?.config?.length || 0;
|
||||
|
||||
const newProcessorData = {
|
||||
id: values.name.replace(/\s/g, ''),
|
||||
orderId: Number(totalDataLength || 0) + 1,
|
||||
type: processorType,
|
||||
enabled: true,
|
||||
...values,
|
||||
};
|
||||
|
||||
if (isEdit && selectedProcessorData && expandedPipelineData?.config) {
|
||||
const findRecordIndex = getRecordIndex(
|
||||
expandedPipelineData?.config,
|
||||
selectedProcessorData,
|
||||
'id',
|
||||
);
|
||||
|
||||
const updatedProcessorData = {
|
||||
id: values.name.replace(/\s/g, ''),
|
||||
orderId: expandedPipelineData?.config?.[findRecordIndex].orderId,
|
||||
type: processorType,
|
||||
enabled: expandedPipelineData?.config?.[findRecordIndex].enabled,
|
||||
output: expandedPipelineData?.config?.[findRecordIndex].output,
|
||||
...values,
|
||||
};
|
||||
|
||||
const editedData = getEditedDataSource(
|
||||
expandedPipelineData.config,
|
||||
selectedProcessorData,
|
||||
'name',
|
||||
updatedProcessorData,
|
||||
);
|
||||
|
||||
const modifiedProcessorData = { ...expandedPipelineData };
|
||||
|
||||
modifiedProcessorData.config = editedData;
|
||||
|
||||
setExpandedPipelineData(modifiedProcessorData);
|
||||
}
|
||||
if (isAdd && expandedPipelineData) {
|
||||
const modifiedProcessorData = {
|
||||
...expandedPipelineData,
|
||||
};
|
||||
if (
|
||||
modifiedProcessorData.config !== undefined &&
|
||||
modifiedProcessorData.config
|
||||
) {
|
||||
modifiedProcessorData.config = [
|
||||
...modifiedProcessorData.config,
|
||||
newProcessorData,
|
||||
];
|
||||
if (totalDataLength > 0) {
|
||||
modifiedProcessorData.config[totalDataLength - 1].output =
|
||||
newProcessorData.id;
|
||||
}
|
||||
}
|
||||
setExpandedPipelineData(modifiedProcessorData);
|
||||
}
|
||||
setActionType(undefined);
|
||||
handleProcessorType(DEFAULT_PROCESSOR_TYPE);
|
||||
};
|
||||
|
||||
const onCancelModal = (): void => {
|
||||
setActionType(undefined);
|
||||
handleProcessorType(DEFAULT_PROCESSOR_TYPE);
|
||||
};
|
||||
|
||||
const modalTitle = useMemo(
|
||||
(): string =>
|
||||
isEdit
|
||||
? `${t('edit_processor')} ${selectedProcessorData?.name}`
|
||||
: t('create_processor'),
|
||||
[isEdit, selectedProcessorData?.name, t],
|
||||
);
|
||||
|
||||
const onOkModalHandler = useCallback(
|
||||
() => setShowSaveButton(ActionMode.Editing),
|
||||
[setShowSaveButton],
|
||||
);
|
||||
|
||||
const isOpen = useMemo(() => isEdit || isAdd, [isAdd, isEdit]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<ModalTitle level={4}>{modalTitle}</ModalTitle>}
|
||||
centered
|
||||
open={isOpen}
|
||||
width={800}
|
||||
footer={null}
|
||||
onCancel={onCancelModal}
|
||||
>
|
||||
<Divider plain />
|
||||
<Form
|
||||
name="add-new-processor"
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
>
|
||||
<TypeSelect value={processorType} onChange={handleProcessorType} />
|
||||
{renderProcessorForm(processorType)}
|
||||
<Divider plain />
|
||||
<Form.Item>
|
||||
<ModalButtonWrapper>
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
onClick={onOkModalHandler}
|
||||
>
|
||||
{isEdit ? t('update') : t('create')}
|
||||
</Button>
|
||||
<Button key="cancel" onClick={onCancelModal}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</ModalButtonWrapper>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
AddNewProcessor.defaultProps = {
|
||||
selectedProcessorData: undefined,
|
||||
expandedPipelineData: {},
|
||||
};
|
||||
|
||||
interface AddNewProcessorProps {
|
||||
isActionType: string;
|
||||
setActionType: (actionType?: ActionType) => void;
|
||||
selectedProcessorData?: ProcessorData;
|
||||
setShowSaveButton: (actionMode: ActionMode) => void;
|
||||
expandedPipelineData?: PipelineData;
|
||||
setExpandedPipelineData: (data: PipelineData) => void;
|
||||
}
|
||||
|
||||
export default AddNewProcessor;
|
@ -0,0 +1,46 @@
|
||||
import { Avatar, Select } from 'antd';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const PipelineIndexIcon = styled(Avatar)`
|
||||
background-color: ${themeColors.navyBlue};
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.375rem;
|
||||
`;
|
||||
|
||||
export const ProcessorTypeWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1.5rem;
|
||||
`;
|
||||
|
||||
export const ProcessorTypeContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 0.5rem;
|
||||
gap: 0.313rem;
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding: 0rem;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const FormWrapper = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const ProcessorType = styled.span`
|
||||
padding-bottom: 0.5rem;
|
||||
`;
|
||||
|
||||
export const StyledSelect = styled(Select)`
|
||||
width: 12.5rem;
|
||||
`;
|
@ -0,0 +1,9 @@
|
||||
import { processorFields, ProcessorFormField } from './config';
|
||||
import NameInput from './FormFields/NameInput';
|
||||
|
||||
export const renderProcessorForm = (
|
||||
processorType: string,
|
||||
): Array<JSX.Element> =>
|
||||
processorFields[processorType]?.map((fieldName: ProcessorFormField) => (
|
||||
<NameInput key={fieldName.id} fieldData={fieldName} />
|
||||
));
|
@ -0,0 +1,24 @@
|
||||
import { ActionMode } from 'types/api/pipeline/def';
|
||||
|
||||
import { ModeAndConfigWrapper } from './styles';
|
||||
|
||||
function ModeAndConfiguration({
|
||||
isActionMode,
|
||||
verison,
|
||||
}: ModeAndConfigurationType): JSX.Element {
|
||||
const actionMode = isActionMode === ActionMode.Editing;
|
||||
|
||||
return (
|
||||
<ModeAndConfigWrapper>
|
||||
Mode: <span>{actionMode ? 'Editing' : 'Viewing'}</span>
|
||||
<div>Configuration Version: {verison}</div>
|
||||
</ModeAndConfigWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ModeAndConfigurationType {
|
||||
isActionMode: string;
|
||||
verison: string | number;
|
||||
}
|
||||
|
||||
export default ModeAndConfiguration;
|
@ -0,0 +1,269 @@
|
||||
import { PlusCircleOutlined } from '@ant-design/icons';
|
||||
import { TableLocale } from 'antd/es/table/interface';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActionMode,
|
||||
ActionType,
|
||||
PipelineData,
|
||||
ProcessorData,
|
||||
} from 'types/api/pipeline/def';
|
||||
|
||||
import { tableComponents } from '../config';
|
||||
import { ModalFooterTitle } from '../styles';
|
||||
import { AlertMessage } from '.';
|
||||
import { processorColumns } from './config';
|
||||
import { FooterButton, StyledTable } from './styles';
|
||||
import DragAction from './TableComponents/DragAction';
|
||||
import PipelineActions from './TableComponents/PipelineActions';
|
||||
import {
|
||||
getEditedDataSource,
|
||||
getProcessorUpdatedRow,
|
||||
getRecordIndex,
|
||||
getTableColumn,
|
||||
} from './utils';
|
||||
|
||||
function PipelineExpandView({
|
||||
handleAlert,
|
||||
setActionType,
|
||||
processorEditAction,
|
||||
isActionMode,
|
||||
setShowSaveButton,
|
||||
expandedPipelineData,
|
||||
setExpandedPipelineData,
|
||||
prevPipelineData,
|
||||
}: PipelineExpandViewProps): JSX.Element {
|
||||
const { t } = useTranslation(['pipeline']);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const isEditingActionMode = isActionMode === ActionMode.Editing;
|
||||
|
||||
const deleteProcessorHandler = useCallback(
|
||||
(record: ProcessorData) => (): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
if (expandedPipelineData && expandedPipelineData?.config) {
|
||||
const filteredData = expandedPipelineData?.config.filter(
|
||||
(item: ProcessorData) => item.id !== record.id,
|
||||
);
|
||||
const pipelineData = { ...expandedPipelineData };
|
||||
pipelineData.config = filteredData;
|
||||
pipelineData.config.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
});
|
||||
for (let i = 0; i < pipelineData.config.length - 1; i += 1) {
|
||||
pipelineData.config[i].output = pipelineData.config[i + 1].id;
|
||||
}
|
||||
delete pipelineData.config[pipelineData.config.length - 1]?.output;
|
||||
setExpandedPipelineData(pipelineData);
|
||||
}
|
||||
},
|
||||
[expandedPipelineData, setShowSaveButton, setExpandedPipelineData],
|
||||
);
|
||||
|
||||
const processorDeleteAction = useCallback(
|
||||
(record: ProcessorData) => (): void => {
|
||||
handleAlert({
|
||||
title: `${t('delete_processor')} : ${record.name}?`,
|
||||
descrition: t('delete_processor_description'),
|
||||
buttontext: t('delete'),
|
||||
onOk: deleteProcessorHandler(record),
|
||||
});
|
||||
},
|
||||
[handleAlert, deleteProcessorHandler, t],
|
||||
);
|
||||
|
||||
const onSwitchProcessorChange = useCallback(
|
||||
(checked: boolean, record: ProcessorData): void => {
|
||||
if (expandedPipelineData && expandedPipelineData?.config) {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
const findRecordIndex = getRecordIndex(
|
||||
expandedPipelineData?.config,
|
||||
record,
|
||||
'id',
|
||||
);
|
||||
const updateSwitch = {
|
||||
...expandedPipelineData?.config[findRecordIndex],
|
||||
enabled: checked,
|
||||
};
|
||||
const editedData = getEditedDataSource(
|
||||
expandedPipelineData?.config,
|
||||
record,
|
||||
'id',
|
||||
updateSwitch,
|
||||
);
|
||||
const modifiedProcessorData = { ...expandedPipelineData };
|
||||
modifiedProcessorData.config = editedData;
|
||||
|
||||
setExpandedPipelineData(modifiedProcessorData);
|
||||
}
|
||||
},
|
||||
[expandedPipelineData, setExpandedPipelineData, setShowSaveButton],
|
||||
);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const fieldColumns = getTableColumn(processorColumns);
|
||||
if (isEditingActionMode) {
|
||||
fieldColumns.push(
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
render: (_value, record): JSX.Element => (
|
||||
<PipelineActions
|
||||
isPipelineAction={false}
|
||||
editAction={processorEditAction(record)}
|
||||
deleteAction={processorDeleteAction(record)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'enabled',
|
||||
key: 'enabled',
|
||||
render: (value, record) => (
|
||||
<DragAction
|
||||
isEnabled={value}
|
||||
onChange={(checked: boolean): void =>
|
||||
onSwitchProcessorChange(checked, record)
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
return fieldColumns;
|
||||
}, [
|
||||
isEditingActionMode,
|
||||
processorEditAction,
|
||||
processorDeleteAction,
|
||||
onSwitchProcessorChange,
|
||||
]);
|
||||
|
||||
const reorderProcessorRow = useCallback(
|
||||
(updatedRow: ProcessorData[]) => (): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
if (expandedPipelineData) {
|
||||
const modifiedProcessorData = { ...expandedPipelineData };
|
||||
modifiedProcessorData.config = updatedRow;
|
||||
setExpandedPipelineData(modifiedProcessorData);
|
||||
}
|
||||
},
|
||||
[expandedPipelineData, setShowSaveButton, setExpandedPipelineData],
|
||||
);
|
||||
|
||||
const onCancelReorderProcessorRow = useCallback(
|
||||
() => (): void => {
|
||||
if (expandedPipelineData) setExpandedPipelineData(expandedPipelineData);
|
||||
},
|
||||
[expandedPipelineData, setExpandedPipelineData],
|
||||
);
|
||||
|
||||
const moveProcessorRow = useCallback(
|
||||
(dragIndex: number, hoverIndex: number) => {
|
||||
if (expandedPipelineData?.config && isEditingActionMode) {
|
||||
const updatedRow = getProcessorUpdatedRow(
|
||||
expandedPipelineData?.config,
|
||||
dragIndex,
|
||||
hoverIndex,
|
||||
);
|
||||
handleAlert({
|
||||
title: t('reorder_processor'),
|
||||
descrition: t('reorder_processor_description'),
|
||||
buttontext: t('reorder'),
|
||||
onOk: reorderProcessorRow(updatedRow),
|
||||
onCancel: onCancelReorderProcessorRow(),
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
expandedPipelineData?.config,
|
||||
isEditingActionMode,
|
||||
handleAlert,
|
||||
t,
|
||||
reorderProcessorRow,
|
||||
onCancelReorderProcessorRow,
|
||||
],
|
||||
);
|
||||
|
||||
const addNewProcessorHandler = useCallback((): void => {
|
||||
setActionType(ActionType.AddProcessor);
|
||||
}, [setActionType]);
|
||||
|
||||
const footer = useCallback((): JSX.Element | undefined => {
|
||||
if (prevPipelineData.length === 0 || isEditingActionMode) {
|
||||
return (
|
||||
<FooterButton type="link" onClick={addNewProcessorHandler}>
|
||||
<PlusCircleOutlined />
|
||||
<ModalFooterTitle>{t('add_new_processor')}</ModalFooterTitle>
|
||||
</FooterButton>
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}, [isEditingActionMode, prevPipelineData, addNewProcessorHandler, t]);
|
||||
|
||||
const onRowHandler = (
|
||||
_data: ProcessorData,
|
||||
index?: number,
|
||||
): React.HTMLAttributes<unknown> =>
|
||||
({
|
||||
index,
|
||||
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 />,
|
||||
});
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<StyledTable
|
||||
locale={getLocales()}
|
||||
isDarkMode={isDarkMode}
|
||||
showHeader={false}
|
||||
columns={columns}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
components={tableComponents}
|
||||
dataSource={processorData}
|
||||
pagination={false}
|
||||
onRow={onRowHandler}
|
||||
footer={footer}
|
||||
/>
|
||||
</DndProvider>
|
||||
);
|
||||
}
|
||||
|
||||
PipelineExpandView.defaultProps = {
|
||||
expandedPipelineData: {},
|
||||
};
|
||||
|
||||
interface PipelineExpandViewProps {
|
||||
handleAlert: (props: AlertMessage) => void;
|
||||
setActionType: (actionType?: ActionType) => void;
|
||||
processorEditAction: (record: ProcessorData) => () => void;
|
||||
isActionMode: string;
|
||||
setShowSaveButton: (actionMode: ActionMode) => void;
|
||||
expandedPipelineData?: PipelineData;
|
||||
setExpandedPipelineData: (data: PipelineData) => void;
|
||||
prevPipelineData: Array<PipelineData>;
|
||||
}
|
||||
|
||||
export default PipelineExpandView;
|
@ -0,0 +1,33 @@
|
||||
import { Button } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SaveConfigWrapper } from './styles';
|
||||
|
||||
function SaveConfigButton({
|
||||
onSaveConfigurationHandler,
|
||||
onCancelConfigurationHandler,
|
||||
}: SaveConfigButtonTypes): JSX.Element {
|
||||
const { t } = useTranslation('pipeline');
|
||||
|
||||
return (
|
||||
<SaveConfigWrapper>
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
onClick={onSaveConfigurationHandler}
|
||||
>
|
||||
{t('save_configuration')}
|
||||
</Button>
|
||||
<Button key="cancel" onClick={onCancelConfigurationHandler}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</SaveConfigWrapper>
|
||||
);
|
||||
}
|
||||
export interface SaveConfigButtonTypes {
|
||||
onSaveConfigurationHandler: VoidFunction;
|
||||
onCancelConfigurationHandler: VoidFunction;
|
||||
}
|
||||
|
||||
export default SaveConfigButton;
|
@ -0,0 +1,21 @@
|
||||
import { HolderOutlined } from '@ant-design/icons';
|
||||
import { Switch } from 'antd';
|
||||
|
||||
import { holdIconStyle } from '../config';
|
||||
import { LastActionColumn } from '../styles';
|
||||
|
||||
function DragAction({ isEnabled, onChange }: DragActionProps): JSX.Element {
|
||||
return (
|
||||
<LastActionColumn>
|
||||
<Switch defaultChecked={isEnabled} onChange={onChange} />
|
||||
<HolderOutlined style={holdIconStyle} />
|
||||
</LastActionColumn>
|
||||
);
|
||||
}
|
||||
|
||||
interface DragActionProps {
|
||||
isEnabled: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
export default DragAction;
|
@ -0,0 +1,28 @@
|
||||
import { IconListStyle } from '../styles';
|
||||
import DeleteAction from './TableActions/DeleteAction';
|
||||
import EditAction from './TableActions/EditAction';
|
||||
// import ViewAction from './TableActions/ViewAction';
|
||||
|
||||
function PipelineActions({
|
||||
isPipelineAction,
|
||||
editAction,
|
||||
deleteAction,
|
||||
}: PipelineActionsProps): JSX.Element {
|
||||
return (
|
||||
<IconListStyle>
|
||||
<EditAction editAction={editAction} isPipelineAction={isPipelineAction} />
|
||||
{/* <ViewAction isPipelineAction={isPipelineAction} /> */}
|
||||
<DeleteAction
|
||||
deleteAction={deleteAction}
|
||||
isPipelineAction={isPipelineAction}
|
||||
/>
|
||||
</IconListStyle>
|
||||
);
|
||||
}
|
||||
|
||||
export interface PipelineActionsProps {
|
||||
isPipelineAction: boolean;
|
||||
editAction: VoidFunction;
|
||||
deleteAction: VoidFunction;
|
||||
}
|
||||
export default PipelineActions;
|
@ -0,0 +1,23 @@
|
||||
import { DeleteFilled } from '@ant-design/icons';
|
||||
|
||||
import { iconStyle, smallIconStyle } from '../../config';
|
||||
|
||||
function DeleteAction({
|
||||
isPipelineAction,
|
||||
deleteAction,
|
||||
}: DeleteActionProps): JSX.Element {
|
||||
if (isPipelineAction) {
|
||||
return <DeleteFilled onClick={deleteAction} style={iconStyle} />;
|
||||
}
|
||||
return (
|
||||
<span key="delete-action">
|
||||
<DeleteFilled onClick={deleteAction} style={smallIconStyle} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export interface DeleteActionProps {
|
||||
isPipelineAction: boolean;
|
||||
deleteAction: VoidFunction;
|
||||
}
|
||||
export default DeleteAction;
|
@ -0,0 +1,23 @@
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
|
||||
import { iconStyle, smallIconStyle } from '../../config';
|
||||
|
||||
function EditAction({
|
||||
isPipelineAction,
|
||||
editAction,
|
||||
}: EditActionProps): JSX.Element {
|
||||
if (isPipelineAction) {
|
||||
return <EditOutlined style={iconStyle} onClick={editAction} />;
|
||||
}
|
||||
return (
|
||||
<span key="edit-action">
|
||||
<EditOutlined style={smallIconStyle} onClick={editAction} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export interface EditActionProps {
|
||||
isPipelineAction: boolean;
|
||||
editAction: VoidFunction;
|
||||
}
|
||||
export default EditAction;
|
@ -0,0 +1,19 @@
|
||||
import { CopyFilled, EyeFilled } from '@ant-design/icons';
|
||||
|
||||
import { iconStyle, smallIconStyle } from '../../config';
|
||||
|
||||
function ViewAction({ isPipelineAction }: ViewActionProps): JSX.Element {
|
||||
if (isPipelineAction) {
|
||||
return <EyeFilled style={iconStyle} />;
|
||||
}
|
||||
return (
|
||||
<span key="view-action">
|
||||
<CopyFilled style={smallIconStyle} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ViewActionProps {
|
||||
isPipelineAction: boolean;
|
||||
}
|
||||
export default ViewAction;
|
@ -0,0 +1,28 @@
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import { PipelineData } from 'types/api/pipeline/def';
|
||||
|
||||
function TableExpandIcon({
|
||||
expanded,
|
||||
onExpand,
|
||||
record,
|
||||
}: TableExpandIconProps): JSX.Element {
|
||||
const handleOnExpand = (
|
||||
e: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
): void => {
|
||||
onExpand(record, e);
|
||||
};
|
||||
|
||||
if (expanded) {
|
||||
return <DownOutlined onClick={handleOnExpand} />;
|
||||
}
|
||||
return <RightOutlined onClick={handleOnExpand} />;
|
||||
}
|
||||
|
||||
interface TableExpandIconProps {
|
||||
expanded: boolean;
|
||||
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void;
|
||||
record: PipelineData;
|
||||
}
|
||||
|
||||
export default TableExpandIcon;
|
@ -0,0 +1,19 @@
|
||||
import { Tag } from 'antd';
|
||||
|
||||
function Tags({ tags }: TagsProps): JSX.Element {
|
||||
return (
|
||||
<span>
|
||||
{tags?.map((tag) => (
|
||||
<Tag color="magenta" key={tag}>
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
interface TagsProps {
|
||||
tags: Array<string>;
|
||||
}
|
||||
|
||||
export default Tags;
|
@ -0,0 +1,41 @@
|
||||
import dayjs from 'dayjs';
|
||||
import React from 'react';
|
||||
import { PipelineData, ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
import { PipelineIndexIcon } from '../AddNewProcessor/styles';
|
||||
import { ColumnDataStyle, ListDataStyle, ProcessorIndexIcon } from '../styles';
|
||||
|
||||
const componentMap: ComponentMap = {
|
||||
orderId: ({ record }) => <PipelineIndexIcon>{record}</PipelineIndexIcon>,
|
||||
createdAt: ({ record }) => (
|
||||
<ColumnDataStyle>
|
||||
{dayjs(record).locale('en').format('MMMM DD, YYYY hh:mm A')}
|
||||
</ColumnDataStyle>
|
||||
),
|
||||
id: ({ record }) => <ProcessorIndexIcon>{record}</ProcessorIndexIcon>,
|
||||
name: ({ record }) => <ListDataStyle>{record}</ListDataStyle>,
|
||||
};
|
||||
|
||||
function TableComponents({
|
||||
columnKey,
|
||||
record,
|
||||
}: TableComponentsProps): JSX.Element {
|
||||
const Component =
|
||||
componentMap[columnKey] ??
|
||||
(({ record }): JSX.Element => <ColumnDataStyle>{record}</ColumnDataStyle>);
|
||||
|
||||
return <Component record={record} />;
|
||||
}
|
||||
|
||||
type ComponentMap = {
|
||||
[key: string]: React.FC<{ record: Record }>;
|
||||
};
|
||||
|
||||
export type Record = PipelineData['orderId'] & ProcessorData;
|
||||
|
||||
interface TableComponentsProps {
|
||||
columnKey: string;
|
||||
record: Record;
|
||||
}
|
||||
|
||||
export default TableComponents;
|
131
frontend/src/container/PipelinePage/PipelineListsView/config.ts
Normal file
131
frontend/src/container/PipelinePage/PipelineListsView/config.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table/interface';
|
||||
import {
|
||||
HistoryData,
|
||||
PipelineData,
|
||||
ProcessorData,
|
||||
} from 'types/api/pipeline/def';
|
||||
|
||||
import DeploymentStage from '../Layouts/ChangeHistory/DeploymentStage';
|
||||
import DeploymentTime from '../Layouts/ChangeHistory/DeploymentTime';
|
||||
import DescriptionTextArea from './AddNewPipeline/FormFields/DescriptionTextArea';
|
||||
import NameInput from './AddNewPipeline/FormFields/NameInput';
|
||||
|
||||
export const pipelineFields = [
|
||||
{
|
||||
id: 1,
|
||||
fieldName: 'Filter',
|
||||
placeholder: 'search_pipeline_placeholder',
|
||||
name: 'filter',
|
||||
component: NameInput,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Name',
|
||||
placeholder: 'pipeline_name_placeholder',
|
||||
name: 'name',
|
||||
component: NameInput,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
fieldName: 'Description',
|
||||
placeholder: 'pipeline_description_placeholder',
|
||||
name: 'description',
|
||||
component: DescriptionTextArea,
|
||||
},
|
||||
];
|
||||
|
||||
export const tagInputStyle: React.CSSProperties = {
|
||||
width: 78,
|
||||
verticalAlign: 'top',
|
||||
flex: 1,
|
||||
};
|
||||
|
||||
export const pipelineColumns: Array<
|
||||
ColumnType<PipelineData> | ColumnGroupType<PipelineData>
|
||||
> = [
|
||||
{
|
||||
key: 'orderId',
|
||||
title: '',
|
||||
dataIndex: 'orderId',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Pipeline Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
key: 'filter',
|
||||
title: 'Filters',
|
||||
dataIndex: 'filter',
|
||||
},
|
||||
|
||||
{
|
||||
key: 'createdAt',
|
||||
title: 'Last Edited',
|
||||
dataIndex: 'createdAt',
|
||||
},
|
||||
{
|
||||
key: 'createdBy',
|
||||
title: 'Edited By',
|
||||
dataIndex: 'createdBy',
|
||||
},
|
||||
];
|
||||
|
||||
export const processorColumns: Array<
|
||||
ColumnType<ProcessorData> | ColumnGroupType<ProcessorData>
|
||||
> = [
|
||||
{
|
||||
key: 'id',
|
||||
title: '',
|
||||
dataIndex: 'orderId',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: '',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
];
|
||||
|
||||
export const changeHistoryColumns: Array<
|
||||
ColumnType<HistoryData> | ColumnGroupType<HistoryData>
|
||||
> = [
|
||||
{
|
||||
key: 'version',
|
||||
title: 'Version',
|
||||
dataIndex: 'version',
|
||||
},
|
||||
{
|
||||
title: 'Deployment Stage',
|
||||
key: 'deployStatus',
|
||||
dataIndex: 'deployStatus',
|
||||
render: DeploymentStage,
|
||||
},
|
||||
{
|
||||
key: 'deployResult',
|
||||
title: 'Last Deploy Message',
|
||||
dataIndex: 'deployResult',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
title: 'Last Deployed Time',
|
||||
dataIndex: 'createdAt',
|
||||
render: DeploymentTime,
|
||||
},
|
||||
{
|
||||
key: 'createdByName',
|
||||
title: 'Edited by',
|
||||
dataIndex: 'createdByName',
|
||||
},
|
||||
];
|
||||
|
||||
export const formValidationRules = [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const iconStyle = { fontSize: '1.5rem' };
|
||||
export const smallIconStyle = { fontSize: '1rem' };
|
||||
export const holdIconStyle = { ...iconStyle, cursor: 'move' };
|
467
frontend/src/container/PipelinePage/PipelineListsView/index.tsx
Normal file
467
frontend/src/container/PipelinePage/PipelineListsView/index.tsx
Normal file
@ -0,0 +1,467 @@
|
||||
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';
|
||||
import React, { useCallback, useEffect, 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 TableExpandIcon from './TableComponents/TableExpandIcon';
|
||||
import {
|
||||
getDataOnSearch,
|
||||
getEditedDataSource,
|
||||
getElementFromArray,
|
||||
getRecordIndex,
|
||||
getTableColumn,
|
||||
getUpdatedRow,
|
||||
} from './utils';
|
||||
|
||||
function PipelineListsView({
|
||||
isActionType,
|
||||
setActionType,
|
||||
isActionMode,
|
||||
setActionMode,
|
||||
piplineData,
|
||||
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(piplineData?.pipelines),
|
||||
);
|
||||
const [currPipelineData, setCurrPipelineData] = useState<Array<PipelineData>>(
|
||||
cloneDeep(piplineData?.pipelines),
|
||||
);
|
||||
const [
|
||||
expandedPipelineData,
|
||||
setExpandedPipelineData,
|
||||
] = useState<PipelineData>();
|
||||
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;
|
||||
|
||||
useEffect(() => {
|
||||
if (pipelineSearchValue === '') setCurrPipelineData(piplineData?.pipelines);
|
||||
if (pipelineSearchValue !== '') {
|
||||
const filterData = piplineData?.pipelines.filter((data: PipelineData) =>
|
||||
getDataOnSearch(data as never, pipelineSearchValue),
|
||||
);
|
||||
setCurrPipelineData(filterData);
|
||||
}
|
||||
}, [pipelineSearchValue, piplineData?.pipelines]);
|
||||
|
||||
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
|
||||
isPipelineAction
|
||||
editAction={pipelineEditAction(record)}
|
||||
deleteAction={pipelineDeleteAction(record)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'enabled',
|
||||
key: 'enabled',
|
||||
render: (value, record) => (
|
||||
<DragAction
|
||||
isEnabled={value}
|
||||
onChange={(checked: boolean): void =>
|
||||
onSwitchPipelineChange(checked, 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 updatedRow = getUpdatedRow(currPipelineData, dragIndex, hoverIndex);
|
||||
updatedRow.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(updatedRow),
|
||||
onCancel: onCancelPipelineSequence(rawData),
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
currPipelineData,
|
||||
isEditingActionMode,
|
||||
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,
|
||||
],
|
||||
);
|
||||
|
||||
const onExpand = useCallback(
|
||||
(expanded: boolean, record: PipelineData): void => {
|
||||
const keys = [];
|
||||
if (expanded && record.id) {
|
||||
keys.push(record?.id);
|
||||
}
|
||||
setExpandedRowKeys(keys);
|
||||
setExpandedPipelineData(record);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
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;
|
||||
if (
|
||||
expandedPipelineData !== undefined &&
|
||||
item.id === expandedPipelineData?.id
|
||||
) {
|
||||
pipelineData.config = expandedPipelineData?.config;
|
||||
}
|
||||
pipelineData.config = item.config;
|
||||
return pipelineData;
|
||||
});
|
||||
modifiedPipelineData.forEach((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,
|
||||
expandedPipelineData,
|
||||
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}
|
||||
verison={piplineData?.version}
|
||||
/>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
expandedRowRender={expandedRowView}
|
||||
expandable={expandableConfig}
|
||||
components={tableComponents}
|
||||
dataSource={currPipelineData}
|
||||
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;
|
||||
piplineData: 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;
|
113
frontend/src/container/PipelinePage/PipelineListsView/styles.ts
Normal file
113
frontend/src/container/PipelinePage/PipelineListsView/styles.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { Avatar, Button, Table, Typography } from 'antd';
|
||||
import { TableProps } from 'antd/lib/table';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterButton = styled(Button)`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-left: 6.2rem;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
`;
|
||||
|
||||
export const IconListStyle = styled.div`
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
export const ColumnDataStyle = styled.span`
|
||||
font-size: 0.75rem;
|
||||
`;
|
||||
|
||||
export const ListDataStyle = styled.div`
|
||||
margin: 0.125rem;
|
||||
padding: 0.313rem;
|
||||
border: none;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.25rem;
|
||||
`;
|
||||
|
||||
export const ProcessorIndexIcon = styled(Avatar)`
|
||||
background-color: ${themeColors.navyBlue};
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 0.813rem;
|
||||
font-weight: 400;
|
||||
`;
|
||||
|
||||
export const StyledTable: React.FC<
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
TableProps<any> & { isDarkMode: boolean }
|
||||
> = styled(Table)`
|
||||
.ant-table-tbody > tr > td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:last-child > td {
|
||||
border: none;
|
||||
}
|
||||
.ant-table-content {
|
||||
background: ${({ isDarkMode }: { isDarkMode: boolean }): StyledCSS =>
|
||||
isDarkMode ? themeColors.neroBlack : themeColors.snowWhite};
|
||||
}
|
||||
`;
|
||||
|
||||
export const AlertContentWrapper = styled.div`
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`;
|
||||
|
||||
export const AlertModalTitle = styled.h1`
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1rem;
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
margin-top: 3rem;
|
||||
`;
|
||||
|
||||
export const LastActionColumn = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1.25rem;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const ModalTitle = styled(Typography.Title)`
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.5rem;
|
||||
`;
|
||||
|
||||
export const ModalButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 0.625rem;
|
||||
`;
|
||||
|
||||
export const ModeAndConfigWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
color: ${themeColors.gamboge};
|
||||
margin: 0.125rem;
|
||||
padding: 0.313rem;
|
||||
`;
|
||||
|
||||
export const SaveConfigWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 0.938rem;
|
||||
margin-top: 1.25rem;
|
||||
`;
|
@ -0,0 +1,95 @@
|
||||
import { ColumnType } from 'antd/lib/table/interface';
|
||||
import dayjs from 'dayjs';
|
||||
import update from 'react-addons-update';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
import TableComponents, { Record } from './TableComponents';
|
||||
|
||||
export function getElementFromArray<T>(
|
||||
arr: Array<T>,
|
||||
target: T,
|
||||
key: keyof T,
|
||||
): Array<T> {
|
||||
return arr.filter((data) => data[key] !== target?.[key]);
|
||||
}
|
||||
|
||||
export function getRecordIndex<T>(
|
||||
arr: Array<T>,
|
||||
target: T,
|
||||
key: keyof T,
|
||||
): number {
|
||||
return arr?.findIndex((item) => item[key] === target?.[key]);
|
||||
}
|
||||
|
||||
export function getUpdatedRow<T>(
|
||||
data: Array<T>,
|
||||
dragIndex: number,
|
||||
hoverIndex: number,
|
||||
): Array<T> {
|
||||
return update(data, {
|
||||
$splice: [
|
||||
[dragIndex, 1],
|
||||
[hoverIndex, 0, data[dragIndex]],
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getTableColumn<T>(
|
||||
columnData: Array<ColumnType<T>>,
|
||||
): Array<ColumnType<T>> {
|
||||
return columnData.map(({ title, key, dataIndex, ellipsis, width }) => ({
|
||||
title,
|
||||
dataIndex,
|
||||
key,
|
||||
align: key === 'id' ? 'right' : 'left',
|
||||
ellipsis,
|
||||
width,
|
||||
render: (record: Record): JSX.Element => (
|
||||
<TableComponents columnKey={String(key)} record={record} />
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
export function getEditedDataSource<T>(
|
||||
arr: Array<T>,
|
||||
target: T,
|
||||
key: keyof T,
|
||||
editedArr: T,
|
||||
): Array<T> {
|
||||
return arr?.map((data) => (data[key] === target?.[key] ? editedArr : data));
|
||||
}
|
||||
|
||||
export function getDataOnSearch(
|
||||
data: {
|
||||
[key: string]: never;
|
||||
},
|
||||
searchValue: string,
|
||||
): boolean {
|
||||
return Object.keys(data).some((key) =>
|
||||
key === 'createdAt'
|
||||
? dayjs(data[key])
|
||||
.locale('en')
|
||||
.format('MMMM DD, YYYY hh:mm A')
|
||||
.includes(searchValue)
|
||||
: String(data[key]).toLowerCase().includes(searchValue.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
export function getProcessorUpdatedRow<T extends ProcessorData>(
|
||||
processorData: Array<T>,
|
||||
dragIndex: number,
|
||||
hoverIndex: number,
|
||||
): Array<T> {
|
||||
const data = processorData;
|
||||
const item = data.splice(dragIndex, 1)[0];
|
||||
data.splice(hoverIndex, 0, item);
|
||||
data.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
});
|
||||
for (let i = 0; i < data.length - 1; i += 1) {
|
||||
data[i].output = data[i + 1].id;
|
||||
}
|
||||
delete data[data.length - 1].output;
|
||||
return data;
|
||||
}
|
157
frontend/src/container/PipelinePage/components/TagInput.tsx
Normal file
157
frontend/src/container/PipelinePage/components/TagInput.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import {
|
||||
CloseCircleFilled,
|
||||
ExclamationCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Input, InputRef, message, Modal, Tag, Tooltip } from 'antd';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { tagInputStyle } from '../PipelineListsView/config';
|
||||
import { TagInputWrapper } from './styles';
|
||||
|
||||
function TagInput({
|
||||
setTagsListData,
|
||||
tagsListData,
|
||||
placeHolder,
|
||||
}: TagInputProps): JSX.Element {
|
||||
const [inputVisible, setInputVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [editInputIndex, setEditInputIndex] = useState(-1);
|
||||
const [editInputValue, setEditInputValue] = useState('');
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
const editInputRef = useRef<InputRef>(null);
|
||||
const { t } = useTranslation(['alerts']);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputVisible) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [inputVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
editInputRef.current?.focus();
|
||||
}, [inputValue]);
|
||||
|
||||
const handleClose = (removedTag: string) => (): void => {
|
||||
const newTags = tagsListData?.filter((tag) => tag !== removedTag);
|
||||
setTagsListData(newTags);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleInputConfirm = (): void => {
|
||||
if (inputValue && tagsListData?.indexOf(inputValue) === -1) {
|
||||
setTagsListData([...tagsListData, inputValue]);
|
||||
}
|
||||
setInputVisible(false);
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
const handleEditInputChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
): void => {
|
||||
setEditInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleEditInputConfirm = (): void => {
|
||||
const newTags = [...tagsListData];
|
||||
newTags[editInputIndex] = editInputValue;
|
||||
setTagsListData(newTags);
|
||||
setEditInputIndex(-1);
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
const handleClearAll = (): void => {
|
||||
Modal.confirm({
|
||||
title: 'Confirm',
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: t('remove_label_confirm'),
|
||||
onOk() {
|
||||
setTagsListData([]);
|
||||
message.success(t('remove_label_success'));
|
||||
},
|
||||
okText: t('button_yes'),
|
||||
cancelText: t('button_no'),
|
||||
});
|
||||
};
|
||||
|
||||
const showAllData = tagsListData?.map((tag: string, index: number) => {
|
||||
if (editInputIndex === index) {
|
||||
return (
|
||||
<Input
|
||||
ref={editInputRef}
|
||||
key={tag}
|
||||
style={tagInputStyle}
|
||||
value={editInputValue}
|
||||
onChange={handleEditInputChange}
|
||||
onBlur={handleEditInputConfirm}
|
||||
onPressEnter={handleEditInputConfirm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const isLongTag = tag.length > 20;
|
||||
const tagElem = (
|
||||
<Tag
|
||||
key={tag}
|
||||
closable
|
||||
style={{ userSelect: 'none' }}
|
||||
onClose={handleClose(tag)}
|
||||
>
|
||||
<span
|
||||
onDoubleClick={(e): void => {
|
||||
setEditInputIndex(index);
|
||||
setEditInputValue(tag);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</span>
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? (
|
||||
<Tooltip title={tag} key={tag}>
|
||||
{tagElem}
|
||||
</Tooltip>
|
||||
) : (
|
||||
tagElem
|
||||
);
|
||||
});
|
||||
|
||||
const isButtonVisible = useMemo(
|
||||
() => tagsListData?.length || inputValue.length || inputValue,
|
||||
[inputValue, tagsListData?.length],
|
||||
);
|
||||
|
||||
return (
|
||||
<TagInputWrapper>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
style={tagInputStyle}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputConfirm}
|
||||
onPressEnter={(e): void => {
|
||||
e.preventDefault();
|
||||
handleInputConfirm();
|
||||
}}
|
||||
placeholder={placeHolder}
|
||||
prefix={showAllData}
|
||||
/>
|
||||
|
||||
{isButtonVisible ? (
|
||||
<Button onClick={handleClearAll} icon={<CloseCircleFilled />} type="text" />
|
||||
) : null}
|
||||
</TagInputWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
interface TagInputProps {
|
||||
setTagsListData: (tags: Array<string>) => void;
|
||||
tagsListData: Array<string>;
|
||||
placeHolder: string;
|
||||
}
|
||||
|
||||
export default TagInput;
|
6
frontend/src/container/PipelinePage/components/styles.ts
Normal file
6
frontend/src/container/PipelinePage/components/styles.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TagInputWrapper = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
7
frontend/src/container/PipelinePage/config.ts
Normal file
7
frontend/src/container/PipelinePage/config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import DraggableTableRow from 'components/DraggableTableRow';
|
||||
|
||||
export const tableComponents = {
|
||||
body: {
|
||||
row: DraggableTableRow,
|
||||
},
|
||||
};
|
155
frontend/src/container/PipelinePage/mocks/pipeline.ts
Normal file
155
frontend/src/container/PipelinePage/mocks/pipeline.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { Pipeline, PipelineData } from 'types/api/pipeline/def';
|
||||
|
||||
export const configurationVerison = '1.0';
|
||||
|
||||
export const pipelineMockData: Array<PipelineData> = [
|
||||
{
|
||||
id: '4453c8b0-c0fd-42bf-bf09-7cc1b04ccdc9',
|
||||
orderId: 1,
|
||||
name: 'Apache common parser',
|
||||
alias: 'apachecommonparser',
|
||||
description: 'This is a desc',
|
||||
enabled: false,
|
||||
filter: 'attributes.source == nginx',
|
||||
config: [
|
||||
{
|
||||
orderId: 1,
|
||||
enabled: true,
|
||||
type: 'grok_parser',
|
||||
id: 'grokusecommon',
|
||||
name: 'grok use common asd',
|
||||
output: 'renameauth',
|
||||
parse_to: 'attributes',
|
||||
pattern: '%{COMMONAPACHELOG}',
|
||||
parse_from: 'body',
|
||||
},
|
||||
{
|
||||
orderId: 2,
|
||||
enabled: true,
|
||||
type: 'move',
|
||||
id: 'renameauth',
|
||||
name: 'rename auth',
|
||||
from: 'attributes.auth',
|
||||
to: 'attributes.username',
|
||||
},
|
||||
],
|
||||
createdBy: 'nityananda@signoz.io',
|
||||
createdAt: '2023-03-07T16:56:53.36071141Z',
|
||||
},
|
||||
{
|
||||
id: 'a3675a0c-ff73-4ddb-be39-4351ace69231',
|
||||
orderId: 2,
|
||||
name: 'Moving pipeline new',
|
||||
alias: 'movingpipelinenew',
|
||||
description: 'This is a desc of move',
|
||||
enabled: false,
|
||||
filter: 'attributes.method == POST',
|
||||
config: [
|
||||
{
|
||||
orderId: 1,
|
||||
enabled: true,
|
||||
type: 'copy',
|
||||
id: 'mv1',
|
||||
name: 'mymove',
|
||||
from: 'attributes.method',
|
||||
to: 'attributes.method11',
|
||||
},
|
||||
],
|
||||
createdBy: 'chintan@signoz.io',
|
||||
createdAt: '2023-03-07T16:55:27.789595116Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const pipelineApiResponseMockData: Pipeline = {
|
||||
id: '67ace08a-6b6c-4221-ab58-a5d3bd5eb6f2',
|
||||
version: 5,
|
||||
elementType: 'log_pipelines',
|
||||
createdBy: 'test@signoz.io',
|
||||
active: false,
|
||||
is_valid: false,
|
||||
disabled: false,
|
||||
deployStatus: 'IN_PROGRESS',
|
||||
deployResult: 'Deployment started',
|
||||
lastHash: 'q<><71>҂<EFBFBD>&覣ʝup<75><70>\u0003<30><33><EFBFBD>q<EFBFBD>6<EFBFBD>\u001e<31><ѥIb<49>',
|
||||
lastConf:
|
||||
'[{"id":"a3675a0c-ff73-4ddb-be39-4351ace69231","orderId":"1","name":"Moving pipeline new","alias":"movingpipelinenew","enabled":false,"filter":"attributes.method == POST","config":[{"type":"copy","id":"mv1","name":"mymove","from":"attributes.method","to":"attributes.method11"}],"createdBy":"nityananda@signoz.io","createdAt":"2023-03-07T16:55:27.789595116Z"},{"id":"4453c8b0-c0fd-42bf-bf09-7cc1b04ccdc9","orderId":"2","name":"Apache common parser","alias":"apachecommonparser","enabled":false,"filter":"attributes.source == nginx","config":[{"type":"grok_parser","id":"grokusecommon","name":"grok use common asd","output":"renameauth","parse_to":"attributes","pattern":"%{COMMONAPACHELOG}","parse_from":"body"},{"type":"move","id":"renameauth","name":"rename auth","from":"attributes.auth","to":"attributes.username"}],"createdBy":"nityananda@signoz.io","createdAt":"2023-03-07T16:56:53.36071141Z"}]',
|
||||
pipelines: pipelineMockData,
|
||||
history: [
|
||||
{
|
||||
id: 'e118dedd-e996-455a-9cb2-5bf50b77fc35',
|
||||
version: 1,
|
||||
elementType: 'log_pipelines',
|
||||
createdBy: 'test2@signoz.io',
|
||||
active: false,
|
||||
isValid: false,
|
||||
disabled: false,
|
||||
deployStatus: 'DEPLOYED',
|
||||
deployResult: 'deploy successful',
|
||||
lastHash: '',
|
||||
lastConf: '',
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
createdAt: '2021-03-07T16:56:53.36071141Z',
|
||||
createdByName: 'test2',
|
||||
},
|
||||
{
|
||||
id: '9a98673d-b5db-4281-89d3-d85ed9ffe311',
|
||||
version: 2,
|
||||
elementType: 'log_pipelines',
|
||||
createdBy: 'test3@signoz.io',
|
||||
active: false,
|
||||
isValid: false,
|
||||
disabled: false,
|
||||
deployStatus: 'DEPLOYED',
|
||||
deployResult: 'deploy successful',
|
||||
lastHash: '',
|
||||
lastConf: '',
|
||||
createdAt: '2021-03-07T16:56:53.36071141Z',
|
||||
createdByName: 'test3',
|
||||
},
|
||||
{
|
||||
id: '9fdb0813-f77f-4837-815e-bb6eedd64f68',
|
||||
version: 3,
|
||||
elementType: 'log_pipelines',
|
||||
createdBy: 'nityananda+1@signoz.io',
|
||||
active: false,
|
||||
isValid: false,
|
||||
disabled: false,
|
||||
deployStatus: 'IN_PROGRESS',
|
||||
deployResult: '',
|
||||
lastHash: '',
|
||||
lastConf: '',
|
||||
createdAt: '2021-03-07T16:56:53.36071141Z',
|
||||
createdByName: 'nityananda+1',
|
||||
},
|
||||
{
|
||||
id: '87efb1cf-85b0-4aa4-934e-62a118fa4ec7',
|
||||
version: 4,
|
||||
elementType: 'log_pipelines',
|
||||
createdBy: 'nityananda+2@signoz.io',
|
||||
active: false,
|
||||
isValid: false,
|
||||
disabled: false,
|
||||
deployStatus: 'IN_PROGRESS',
|
||||
deployResult: '',
|
||||
lastHash: '',
|
||||
lastConf: '',
|
||||
createdAt: '2021-03-07T16:56:53.36071141Z',
|
||||
createdByName: 'nityananda+2',
|
||||
},
|
||||
{
|
||||
id: '67ace08a-6b6c-4221-ab58-a5d3bd5eb6f2',
|
||||
version: 5,
|
||||
elementType: 'log_pipelines',
|
||||
createdBy: 'nityananda+4@signoz.io',
|
||||
active: false,
|
||||
isValid: false,
|
||||
disabled: false,
|
||||
deployStatus: 'IN_PROGRESS',
|
||||
deployResult: '',
|
||||
lastHash: '',
|
||||
lastConf: '',
|
||||
createdAt: '2021-03-07T16:56:53.36071141Z',
|
||||
createdByName: 'nityananda+4',
|
||||
},
|
||||
],
|
||||
};
|
32
frontend/src/container/PipelinePage/styles.ts
Normal file
32
frontend/src/container/PipelinePage/styles.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CustomButton = styled(Button)`
|
||||
&&& {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ModalFooterTitle = styled.span`
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
`;
|
||||
|
||||
export const HistoryTableWrapper = styled.div`
|
||||
margin-top: 3rem;
|
||||
`;
|
||||
|
||||
export const IconDataSpan = styled.span`
|
||||
padding: 0.625rem;
|
||||
`;
|
@ -0,0 +1,53 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import { pipelineMockData } from '../mocks/pipeline';
|
||||
import AddNewPipeline from '../PipelineListsView/AddNewPipeline';
|
||||
|
||||
export function matchMedia(): void {
|
||||
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(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
beforeAll(() => {
|
||||
matchMedia();
|
||||
});
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render AddNewPipeline section', () => {
|
||||
const setActionType = jest.fn();
|
||||
const selectedPipelineData = pipelineMockData[0];
|
||||
const isActionType = 'add-pipeline';
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<AddNewPipeline
|
||||
isActionType={isActionType}
|
||||
setActionType={setActionType}
|
||||
selectedPipelineData={selectedPipelineData}
|
||||
setShowSaveButton={jest.fn()}
|
||||
setCurrPipelineData={jest.fn()}
|
||||
currPipelineData={pipelineMockData}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import { pipelineMockData } from '../mocks/pipeline';
|
||||
import AddNewProcessor from '../PipelineListsView/AddNewProcessor';
|
||||
import { matchMedia } from './AddNewPipeline.test';
|
||||
|
||||
beforeAll(() => {
|
||||
matchMedia();
|
||||
});
|
||||
|
||||
const selectedProcessorData = {
|
||||
id: '1',
|
||||
orderId: 1,
|
||||
type: 'grok_parser',
|
||||
name: 'grok use common',
|
||||
output: 'grokusecommon',
|
||||
};
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render AddNewProcessor section', () => {
|
||||
const setActionType = jest.fn();
|
||||
const isActionType = 'add-processor';
|
||||
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<AddNewProcessor
|
||||
isActionType={isActionType}
|
||||
setActionType={setActionType}
|
||||
selectedProcessorData={selectedProcessorData}
|
||||
setShowSaveButton={jest.fn()}
|
||||
expandedPipelineData={pipelineMockData[0]}
|
||||
setExpandedPipelineData={jest.fn()}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import CreatePipelineButton from '../Layouts/Pipeline/CreatePipelineButton';
|
||||
import { pipelineApiResponseMockData } from '../mocks/pipeline';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render CreatePipelineButton section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<CreatePipelineButton
|
||||
setActionType={jest.fn()}
|
||||
isActionMode="viewing-mode"
|
||||
setActionMode={jest.fn()}
|
||||
piplineData={pipelineApiResponseMockData}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import DeleteAction from 'container/PipelinePage/PipelineListsView/TableComponents/TableActions/DeleteAction';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render DeleteAction section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<DeleteAction isPipelineAction deleteAction={jest.fn()} />
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import DragAction from 'container/PipelinePage/PipelineListsView/TableComponents/DragAction';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render DragAction section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<DragAction isEnabled onChange={jest.fn()} />
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import EditAction from 'container/PipelinePage/PipelineListsView/TableComponents/TableActions/EditAction';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render EditAction section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<EditAction isPipelineAction editAction={jest.fn()} />
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import PipelineActions from 'container/PipelinePage/PipelineListsView/TableComponents/PipelineActions';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render PipelineActions section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<PipelineActions
|
||||
isPipelineAction
|
||||
editAction={jest.fn()}
|
||||
deleteAction={jest.fn()}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,38 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import { pipelineMockData } from '../mocks/pipeline';
|
||||
import PipelineExpandView from '../PipelineListsView/PipelineExpandView';
|
||||
import { matchMedia } from './AddNewPipeline.test';
|
||||
|
||||
beforeAll(() => {
|
||||
matchMedia();
|
||||
});
|
||||
|
||||
describe('PipelinePage', () => {
|
||||
it('should render PipelineExpandView section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<PipelineExpandView
|
||||
handleAlert={jest.fn()}
|
||||
setActionType={jest.fn()}
|
||||
processorEditAction={jest.fn()}
|
||||
isActionMode="viewing-mode"
|
||||
setShowSaveButton={jest.fn()}
|
||||
expandedPipelineData={pipelineMockData[0]}
|
||||
setExpandedPipelineData={jest.fn()}
|
||||
prevPipelineData={pipelineMockData}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,62 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
import { Pipeline } from 'types/api/pipeline/def';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import PipelinePageLayout from '../Layouts/Pipeline';
|
||||
import { matchMedia } from './AddNewPipeline.test';
|
||||
|
||||
beforeAll(() => {
|
||||
matchMedia();
|
||||
});
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render PipelinePageLayout section', () => {
|
||||
const pipelinedata: Pipeline = {
|
||||
active: true,
|
||||
createdBy: 'admin',
|
||||
deployResult: 'random_data',
|
||||
deployStatus: 'random_data',
|
||||
disabled: false,
|
||||
elementType: 'random_data',
|
||||
history: [],
|
||||
id: v4(),
|
||||
is_valid: true,
|
||||
lastConf: 'random_data',
|
||||
lastHash: 'random_data',
|
||||
pipelines: [],
|
||||
version: 1,
|
||||
};
|
||||
|
||||
const refetchPipelineLists = jest.fn();
|
||||
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<PipelinePageLayout
|
||||
piplineData={pipelinedata}
|
||||
refetchPipelineLists={refetchPipelineLists}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import PipelinesSearchSection from '../Layouts/Pipeline/PipelinesSearchSection';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render PipelinesSearchSection section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<PipelinesSearchSection setPipelineSearchValue={jest.fn()} />
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { pipelineMockData } from 'container/PipelinePage/mocks/pipeline';
|
||||
import TableExpandIcon from 'container/PipelinePage/PipelineListsView/TableComponents/TableExpandIcon';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render TableExpandIcon section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<TableExpandIcon
|
||||
expanded
|
||||
onExpand={jest.fn()}
|
||||
record={pipelineMockData[0]}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
23
frontend/src/container/PipelinePage/tests/TagInput.test.tsx
Normal file
23
frontend/src/container/PipelinePage/tests/TagInput.test.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import TagInput from '../components/TagInput';
|
||||
|
||||
describe('Pipeline Page', () => {
|
||||
it('should render TagInput section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<TagInput setTagsListData={jest.fn()} tagsListData={[]} placeHolder="" />
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
24
frontend/src/container/PipelinePage/tests/Tags.test.tsx
Normal file
24
frontend/src/container/PipelinePage/tests/Tags.test.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import Tags from 'container/PipelinePage/PipelineListsView/TableComponents/Tags';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
const tags = ['server', 'app'];
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render Tags section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Tags tags={tags} />
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import ViewAction from 'container/PipelinePage/PipelineListsView/TableComponents/TableActions/ViewAction';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
describe('PipelinePage container test', () => {
|
||||
it('should render ViewAction section', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ViewAction isPipelineAction />
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render AddNewPipeline section 1`] = `<DocumentFragment />`;
|
@ -0,0 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render AddNewPipeline section 1`] = `<DocumentFragment />`;
|
||||
|
||||
exports[`PipelinePage container test should render AddNewProcessor section 1`] = `<DocumentFragment />`;
|
@ -0,0 +1,77 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render CreatePipelineButton section 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0.c0.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: end;
|
||||
-webkit-justify-content: flex-end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 2rem;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c1.c1.c1 {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
aria-label="question-circle"
|
||||
class="anticon anticon-question-circle"
|
||||
role="img"
|
||||
style="font-size: 1rem; color: rgba(255, 255, 255, 0.835);"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="question-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 64zm0 708c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm62.9-219.5a48.3 48.3 0 00-30.9 44.8V620c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-21.5c0-23.1 6.7-45.9 19.9-64.9 12.9-18.6 30.9-32.8 52.1-40.9 34-13.1 56-41.6 56-72.7 0-44.1-43.1-80-96-80s-96 35.9-96 80v7.6c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V420c0-39.3 17.2-76 48.4-103.3C430.4 290.4 470 276 512 276s81.6 14.5 111.6 40.7C654.8 344 672 380.7 672 420c0 57.8-38.1 109.8-97.1 132.5z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<button
|
||||
class="ant-btn css-dev-only-do-not-override-1i536d8 ant-btn-default c1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="edit"
|
||||
class="anticon anticon-edit"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="edit"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32zm-622.3-84c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
enter_edit_mode
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,27 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render DeleteAction section 1`] = `
|
||||
<DocumentFragment>
|
||||
<span
|
||||
aria-label="delete"
|
||||
class="anticon anticon-delete"
|
||||
role="img"
|
||||
style="font-size: 1.5rem;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="delete"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M864 256H736v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zm-200 0H360v-72h304v72z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,66 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render DragAction section 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
gap: 1.25rem;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<button
|
||||
aria-checked="true"
|
||||
class="ant-switch css-dev-only-do-not-override-1i536d8 ant-switch-checked"
|
||||
role="switch"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-switch-handle"
|
||||
/>
|
||||
<span
|
||||
class="ant-switch-inner"
|
||||
>
|
||||
<span
|
||||
class="ant-switch-inner-checked"
|
||||
/>
|
||||
<span
|
||||
class="ant-switch-inner-unchecked"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
aria-label="holder"
|
||||
class="anticon anticon-holder"
|
||||
role="img"
|
||||
style="font-size: 1.5rem; cursor: move;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="holder"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,27 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render EditAction section 1`] = `
|
||||
<DocumentFragment>
|
||||
<span
|
||||
aria-label="edit"
|
||||
class="anticon anticon-edit"
|
||||
role="img"
|
||||
style="font-size: 1.5rem;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="edit"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,64 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render PipelineActions section 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
-webkit-box-pack: end;
|
||||
-webkit-justify-content: flex-end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
aria-label="edit"
|
||||
class="anticon anticon-edit"
|
||||
role="img"
|
||||
style="font-size: 1.5rem;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="edit"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
aria-label="delete"
|
||||
class="anticon anticon-delete"
|
||||
role="img"
|
||||
style="font-size: 1.5rem;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="delete"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M864 256H736v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zm-200 0H360v-72h304v72z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,141 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render AddNewPipeline section 1`] = `<DocumentFragment />`;
|
||||
|
||||
exports[`PipelinePage should render PipelineExpandView section 1`] = `
|
||||
<DocumentFragment>
|
||||
.c2 {
|
||||
margin: 0.125rem;
|
||||
padding: 0.313rem;
|
||||
border: none;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background-color: #1668DC;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 0.813rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.c0 .ant-table-tbody > tr > td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.c0 .ant-table-tbody > tr:last-child > td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.c0 .ant-table-content {
|
||||
background: #1d1d1d;
|
||||
}
|
||||
|
||||
<div
|
||||
class="ant-table-wrapper c0 css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<div
|
||||
class="ant-spin-nested-loading css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<div
|
||||
class="ant-spin-container"
|
||||
>
|
||||
<div
|
||||
class="ant-table ant-table-small"
|
||||
>
|
||||
<div
|
||||
class="ant-table-container"
|
||||
>
|
||||
<div
|
||||
class="ant-table-content"
|
||||
>
|
||||
<table
|
||||
style="table-layout: auto;"
|
||||
>
|
||||
<colgroup>
|
||||
<col
|
||||
style="width: 150px;"
|
||||
/>
|
||||
</colgroup>
|
||||
<tbody
|
||||
class="ant-table-tbody"
|
||||
>
|
||||
<tr
|
||||
class="ant-table-row ant-table-row-level-0"
|
||||
data-row-key="grok use common asd"
|
||||
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-1i536d8"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(1) translateX(-50%);"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
grok use common asd
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="ant-table-row ant-table-row-level-0"
|
||||
data-row-key="rename auth"
|
||||
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-1i536d8"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(1) translateX(-50%);"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
rename auth
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-table-footer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,326 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render AddNewPipeline section 1`] = `<DocumentFragment />`;
|
||||
|
||||
exports[`PipelinePage container test should render PipelinePageLayout section 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0.c0.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: end;
|
||||
-webkit-justify-content: flex-end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 2rem;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c1.c1.c1 {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
aria-label="question-circle"
|
||||
class="anticon anticon-question-circle"
|
||||
role="img"
|
||||
style="font-size: 1rem; color: rgba(255, 255, 255, 0.835);"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="question-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 64zm0 708c-22.1 0-40-17.9-40-40s17.9-40 40-40 40 17.9 40 40-17.9 40-40 40zm62.9-219.5a48.3 48.3 0 00-30.9 44.8V620c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8v-21.5c0-23.1 6.7-45.9 19.9-64.9 12.9-18.6 30.9-32.8 52.1-40.9 34-13.1 56-41.6 56-72.7 0-44.1-43.1-80-96-80s-96 35.9-96 80v7.6c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V420c0-39.3 17.2-76 48.4-103.3C430.4 290.4 470 276 512 276s81.6 14.5 111.6 40.7C654.8 344 672 380.7 672 420c0 57.8-38.1 109.8-97.1 132.5z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<button
|
||||
class="ant-btn css-dev-only-do-not-override-1i536d8 ant-btn-primary c1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="plus"
|
||||
class="anticon anticon-plus"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="plus"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<defs>
|
||||
<style />
|
||||
</defs>
|
||||
<path
|
||||
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
|
||||
/>
|
||||
<path
|
||||
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
new_pipeline
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="ant-input-group-wrapper ant-input-search css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<span
|
||||
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-suffix"
|
||||
>
|
||||
<span
|
||||
class="ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<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>
|
||||
</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 {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
-webkit-box-pack: end;
|
||||
-webkit-justify-content: flex-end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
color: #D89614;
|
||||
margin: 0.125rem;
|
||||
padding: 0.313rem;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
Mode:
|
||||
<span>
|
||||
Viewing
|
||||
</span>
|
||||
<div>
|
||||
Configuration Version: 1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-table-wrapper css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<div
|
||||
class="ant-spin-nested-loading css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<div
|
||||
class="ant-spin-container"
|
||||
>
|
||||
<div
|
||||
class="ant-table ant-table-empty"
|
||||
>
|
||||
<div
|
||||
class="ant-table-container"
|
||||
>
|
||||
<div
|
||||
class="ant-table-content"
|
||||
>
|
||||
<table
|
||||
style="table-layout: auto;"
|
||||
>
|
||||
<colgroup>
|
||||
<col
|
||||
class="ant-table-expand-icon-col"
|
||||
/>
|
||||
</colgroup>
|
||||
<thead
|
||||
class="ant-table-thead"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
class="ant-table-cell ant-table-row-expand-icon-cell"
|
||||
/>
|
||||
<th
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
/>
|
||||
<th
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
>
|
||||
Pipeline Name
|
||||
</th>
|
||||
<th
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
>
|
||||
Filters
|
||||
</th>
|
||||
<th
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
>
|
||||
Last Edited
|
||||
</th>
|
||||
<th
|
||||
class="ant-table-cell"
|
||||
style="text-align: left;"
|
||||
>
|
||||
Edited By
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="ant-table-tbody"
|
||||
>
|
||||
<tr
|
||||
class="ant-table-placeholder"
|
||||
draggable="true"
|
||||
>
|
||||
<td
|
||||
class="ant-table-cell"
|
||||
colspan="6"
|
||||
>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-1i536d8 ant-empty ant-empty-normal"
|
||||
>
|
||||
<div
|
||||
class="ant-empty-image"
|
||||
>
|
||||
<svg
|
||||
height="41"
|
||||
viewBox="0 0 64 41"
|
||||
width="64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
transform="translate(0 1)"
|
||||
>
|
||||
<ellipse
|
||||
cx="32"
|
||||
cy="33"
|
||||
fill="#f5f5f5"
|
||||
rx="32"
|
||||
ry="7"
|
||||
/>
|
||||
<g
|
||||
fill-rule="nonzero"
|
||||
stroke="#d9d9d9"
|
||||
>
|
||||
<path
|
||||
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
|
||||
/>
|
||||
<path
|
||||
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
|
||||
fill="#fafafa"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="ant-empty-description"
|
||||
>
|
||||
No data
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-table-footer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,81 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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"
|
||||
>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<span
|
||||
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-suffix"
|
||||
>
|
||||
<span
|
||||
class="ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<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>
|
||||
</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>
|
||||
`;
|
@ -0,0 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render TableExpandIcon section 1`] = `
|
||||
<DocumentFragment>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down"
|
||||
role="img"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Pipeline Page should render TagInput section 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
|
||||
style="width: 78px; vertical-align: top; flex: 1;"
|
||||
>
|
||||
<span
|
||||
class="ant-input-prefix"
|
||||
/>
|
||||
<input
|
||||
class="ant-input css-dev-only-do-not-override-1i536d8"
|
||||
placeholder=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render Tags section 1`] = `
|
||||
<DocumentFragment>
|
||||
<span>
|
||||
<span
|
||||
class="ant-tag ant-tag-magenta css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
server
|
||||
</span>
|
||||
<span
|
||||
class="ant-tag ant-tag-magenta css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
app
|
||||
</span>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -0,0 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PipelinePage container test should render ViewAction section 1`] = `
|
||||
<DocumentFragment>
|
||||
<span
|
||||
aria-label="eye"
|
||||
class="anticon anticon-eye"
|
||||
role="img"
|
||||
style="font-size: 1.5rem;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="eye"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M396 512a112 112 0 10224 0 112 112 0 10-224 0zm546.2-25.8C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM508 688c-97.2 0-176-78.8-176-176s78.8-176 176-176 176 78.8 176 176-78.8 176-176 176z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
88
frontend/src/container/PipelinePage/tests/utils.test.ts
Normal file
88
frontend/src/container/PipelinePage/tests/utils.test.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { pipelineMockData } from '../mocks/pipeline';
|
||||
import {
|
||||
processorFields,
|
||||
processorTypes,
|
||||
} from '../PipelineListsView/AddNewProcessor/config';
|
||||
import { pipelineFields, processorColumns } from '../PipelineListsView/config';
|
||||
import {
|
||||
getEditedDataSource,
|
||||
getElementFromArray,
|
||||
getRecordIndex,
|
||||
getTableColumn,
|
||||
} from '../PipelineListsView/utils';
|
||||
|
||||
describe('Utils testing of Pipeline Page', () => {
|
||||
test('it should be check form field of add pipeline', () => {
|
||||
expect(pipelineFields.length).toBe(3);
|
||||
expect(pipelineFields.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
test('it should be check processor types field of add pipeline', () => {
|
||||
expect(processorTypes.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
test('it should check form field of add processor', () => {
|
||||
Object.keys(processorFields).forEach((key) => {
|
||||
expect(processorFields[key].length).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('it should be check data length of pipeline', () => {
|
||||
expect(pipelineMockData.length).toBe(2);
|
||||
expect(pipelineMockData.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('it should be return filtered data and perform deletion', () => {
|
||||
const filterData = getElementFromArray(
|
||||
pipelineMockData,
|
||||
pipelineMockData[0],
|
||||
'id',
|
||||
);
|
||||
expect(pipelineMockData).not.toEqual(filterData);
|
||||
expect(pipelineMockData[0]).not.toEqual(filterData);
|
||||
});
|
||||
|
||||
test('it should be return index data and perform deletion', () => {
|
||||
const findRecordIndex = getRecordIndex(
|
||||
pipelineMockData,
|
||||
pipelineMockData[0],
|
||||
'id',
|
||||
);
|
||||
expect(pipelineMockData).not.toEqual(findRecordIndex);
|
||||
expect(pipelineMockData[0]).not.toEqual(findRecordIndex);
|
||||
});
|
||||
|
||||
test('it should be return modified column data', () => {
|
||||
const columnData = getTableColumn(processorColumns);
|
||||
expect(processorColumns).not.toEqual(columnData);
|
||||
expect(processorColumns.length).toEqual(columnData.length);
|
||||
});
|
||||
|
||||
test('it should be return modified column data', () => {
|
||||
const findRecordIndex = getRecordIndex(
|
||||
pipelineMockData,
|
||||
pipelineMockData[0],
|
||||
'name',
|
||||
);
|
||||
const updatedPipelineData = {
|
||||
...pipelineMockData[findRecordIndex],
|
||||
name: 'updated name',
|
||||
description: 'changed description',
|
||||
filter: 'value == test',
|
||||
tags: ['test'],
|
||||
};
|
||||
const editedData = getEditedDataSource(
|
||||
pipelineMockData,
|
||||
pipelineMockData[0],
|
||||
'name',
|
||||
updatedPipelineData,
|
||||
);
|
||||
expect(pipelineMockData).not.toEqual(editedData);
|
||||
expect(pipelineMockData.length).toEqual(editedData.length);
|
||||
expect(pipelineMockData[0].name).not.toEqual(editedData[0].name);
|
||||
expect(pipelineMockData[0].description).not.toEqual(
|
||||
editedData[0].description,
|
||||
);
|
||||
expect(pipelineMockData[0].tags).not.toEqual(editedData[0].tags);
|
||||
});
|
||||
});
|
@ -1,8 +1,16 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { themeColors } from 'constants/theme';
|
||||
|
||||
export const styles = { background: '#1f1f1f' };
|
||||
|
||||
export const subMenuStyles = {
|
||||
background: '#1f1f1f',
|
||||
margin: '0rem',
|
||||
width: '100%',
|
||||
color: themeColors.gainsboro,
|
||||
};
|
||||
|
||||
export const routeConfig: Record<string, QueryParams[]> = {
|
||||
[ROUTES.SERVICE_METRICS]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.SERVICE_MAP]: [QueryParams.resourceAttributes],
|
||||
@ -36,4 +44,6 @@ export const routeConfig: Record<string, QueryParams[]> = {
|
||||
[ROUTES.UN_AUTHORIZED]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.PIPELINES]: [QueryParams.resourceAttributes],
|
||||
};
|
||||
|
@ -53,14 +53,14 @@ function SideNav(): JSX.Element {
|
||||
}, [collapsed, dispatch]);
|
||||
|
||||
const onClickHandler = useCallback(
|
||||
(to: string) => {
|
||||
(key: string) => {
|
||||
const params = new URLSearchParams(search);
|
||||
const availableParams = routeConfig[to];
|
||||
const availableParams = routeConfig[key];
|
||||
|
||||
const queryString = getQueryString(availableParams || [], params);
|
||||
|
||||
if (pathname !== to) {
|
||||
history.push(`${to}?${queryString.join('&')}`);
|
||||
if (pathname !== key) {
|
||||
history.push(`${key}?${queryString.join('&')}`);
|
||||
}
|
||||
},
|
||||
[pathname, search],
|
||||
|
@ -68,6 +68,11 @@ const menus: SidebarMenu[] = [
|
||||
// label: 'Views',
|
||||
// },
|
||||
// ],
|
||||
// {
|
||||
// key: ROUTES.PIPELINES,
|
||||
// label: 'Pipelines',
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
{
|
||||
key: ROUTES.ALL_DASHBOARD,
|
||||
|
@ -21,6 +21,7 @@ const breadcrumbNameMap = {
|
||||
[ROUTES.ALL_DASHBOARD]: 'Dashboard',
|
||||
[ROUTES.LOGS]: 'Logs',
|
||||
[ROUTES.LOGS_EXPLORER]: 'Logs Explorer',
|
||||
[ROUTES.PIPELINES]: 'Pipelines',
|
||||
};
|
||||
|
||||
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {
|
||||
|
@ -82,4 +82,5 @@ export const routesToSkip = [
|
||||
ROUTES.ALERTS_NEW,
|
||||
ROUTES.EDIT_ALERTS,
|
||||
ROUTES.LIST_ALL_ALERT,
|
||||
ROUTES.PIPELINES,
|
||||
];
|
||||
|
64
frontend/src/pages/Pipelines/index.tsx
Normal file
64
frontend/src/pages/Pipelines/index.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import type { TabsProps } from 'antd';
|
||||
import { Tabs } from 'antd';
|
||||
import getPipeline from 'api/pipeline/get';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ChangeHistory from 'container/PipelinePage/Layouts/ChangeHistory';
|
||||
import PipelinePage from 'container/PipelinePage/Layouts/Pipeline';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Pipeline } from 'types/api/pipeline/def';
|
||||
|
||||
function Pipelines(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const { notifications } = useNotifications();
|
||||
const {
|
||||
isLoading,
|
||||
data: piplineData,
|
||||
isError,
|
||||
refetch: refetchPipelineLists,
|
||||
} = useQuery(['version', 'latest', 'pipeline'], {
|
||||
queryFn: () =>
|
||||
getPipeline({
|
||||
version: 'latest',
|
||||
}),
|
||||
});
|
||||
|
||||
const tabItems: TabsProps['items'] = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'pipelines',
|
||||
label: `Pipelines`,
|
||||
children: (
|
||||
<PipelinePage
|
||||
refetchPipelineLists={refetchPipelineLists}
|
||||
piplineData={piplineData?.payload as Pipeline}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'change-history',
|
||||
label: `Change History`,
|
||||
children: <ChangeHistory piplineData={piplineData?.payload as Pipeline} />,
|
||||
},
|
||||
],
|
||||
[piplineData?.payload, refetchPipelineLists],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (piplineData?.error && isError) {
|
||||
notifications.error({
|
||||
message: piplineData?.error || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
}, [isError, notifications, piplineData?.error, t]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height="75vh" tip="Loading Pipelines..." />;
|
||||
}
|
||||
|
||||
return <Tabs defaultActiveKey="pipelines" items={tabItems} />;
|
||||
}
|
||||
|
||||
export default Pipelines;
|
76
frontend/src/types/api/pipeline/def.ts
Normal file
76
frontend/src/types/api/pipeline/def.ts
Normal file
@ -0,0 +1,76 @@
|
||||
export interface ProcessorData {
|
||||
type: string;
|
||||
id?: string;
|
||||
orderId: number;
|
||||
name: string;
|
||||
enabled?: boolean;
|
||||
output?: string;
|
||||
parse_to?: string;
|
||||
pattern?: string;
|
||||
parse_from?: string;
|
||||
from?: string;
|
||||
to?: string;
|
||||
regex?: string;
|
||||
on_error?: string;
|
||||
field?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface PipelineData {
|
||||
alias: string;
|
||||
config?: Array<ProcessorData>;
|
||||
createdAt: string;
|
||||
description?: string;
|
||||
createdBy: string;
|
||||
enabled: boolean;
|
||||
filter: string;
|
||||
id?: string;
|
||||
name: string;
|
||||
orderId: number;
|
||||
tags?: Array<string>; // Tags data is missing in API response
|
||||
}
|
||||
|
||||
export interface HistoryData {
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
createdByName: string;
|
||||
deployStatus: string;
|
||||
deployResult: string;
|
||||
disabled: boolean;
|
||||
elementType: string;
|
||||
id: string;
|
||||
isValid: boolean;
|
||||
lastConf: string;
|
||||
lastHash: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface Pipeline {
|
||||
active: boolean;
|
||||
createdBy: string;
|
||||
deployResult: string;
|
||||
deployStatus: string;
|
||||
disabled: boolean;
|
||||
elementType: string;
|
||||
history: Array<HistoryData>;
|
||||
id: string;
|
||||
is_valid: boolean;
|
||||
lastConf: string;
|
||||
lastHash: string;
|
||||
pipelines: Array<PipelineData>;
|
||||
version: string | number;
|
||||
}
|
||||
|
||||
export enum ActionType {
|
||||
AddPipeline = 'add-pipeline',
|
||||
EditPipeline = 'edit-pipeline',
|
||||
AddProcessor = 'add-processor',
|
||||
EditProcessor = 'edit-processor',
|
||||
}
|
||||
|
||||
export enum ActionMode {
|
||||
Viewing = 'viewing-mode',
|
||||
Editing = 'editing-mode',
|
||||
Deploying = 'deploying-mode',
|
||||
}
|
3
frontend/src/types/api/pipeline/get.ts
Normal file
3
frontend/src/types/api/pipeline/get.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type Props = {
|
||||
version: string | number;
|
||||
};
|
5
frontend/src/types/api/pipeline/post.ts
Normal file
5
frontend/src/types/api/pipeline/post.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { PipelineData } from './def';
|
||||
|
||||
export interface Props {
|
||||
data: { pipelines: Array<PipelineData> };
|
||||
}
|
@ -17,7 +17,8 @@ export type ComponentTypes =
|
||||
| 'new_dashboard'
|
||||
| 'new_alert_action'
|
||||
| 'edit_widget'
|
||||
| 'add_panel';
|
||||
| 'add_panel'
|
||||
| 'page_pipelines';
|
||||
|
||||
export const componentPermission: Record<ComponentTypes, ROLES[]> = {
|
||||
current_org_settings: ['ADMIN'],
|
||||
@ -36,6 +37,7 @@ export const componentPermission: Record<ComponentTypes, ROLES[]> = {
|
||||
new_alert_action: ['ADMIN'],
|
||||
edit_widget: ['ADMIN', 'EDITOR'],
|
||||
add_panel: ['ADMIN', 'EDITOR'],
|
||||
page_pipelines: ['ADMIN', 'EDITOR'],
|
||||
};
|
||||
|
||||
export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
@ -72,4 +74,6 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
LOGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LIST_LICENSES: ['ADMIN'],
|
||||
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user