mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-24 05:24:28 +08:00
commit
7603e0ebe0
1
.github/workflows/staging-deployment.yaml
vendored
1
.github/workflows/staging-deployment.yaml
vendored
@ -29,6 +29,7 @@ jobs:
|
||||
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||
docker system prune --force
|
||||
docker pull signoz/signoz-otel-collector:main
|
||||
docker pull signoz/signoz/signoz-schema-migrator:main
|
||||
cd ~/signoz
|
||||
git status
|
||||
git add .
|
||||
|
@ -146,7 +146,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.32.1
|
||||
image: signoz/query-service:0.33.0
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
@ -186,7 +186,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.32.1
|
||||
image: signoz/frontend:0.33.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@ -199,7 +199,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.79.12
|
||||
image: signoz/signoz-otel-collector:0.79.13
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@ -237,7 +237,7 @@ services:
|
||||
- query-service
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:0.79.12
|
||||
image: signoz/signoz-schema-migrator:0.79.13
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@ -250,7 +250,7 @@ services:
|
||||
# - clickhouse-3
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:0.79.12
|
||||
image: signoz/signoz-otel-collector:0.79.13
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
@ -63,38 +63,33 @@ receivers:
|
||||
processors:
|
||||
logstransform/internal:
|
||||
operators:
|
||||
- type: regex_parser
|
||||
id: traceid
|
||||
# https://regex101.com/r/MMfNjk/1
|
||||
regex: '(?i)(trace(-|_||)id("|=| |-|:)*)(?P<trace_id>[A-Fa-f0-9]+)'
|
||||
parse_from: body
|
||||
parse_to: attributes.temp_trace
|
||||
if: 'body matches "(?i)(trace(-|_||)id(\"|=| |-|:)*)(?P<trace_id>[A-Fa-f0-9]+)"'
|
||||
output: spanid
|
||||
- type: regex_parser
|
||||
id: spanid
|
||||
# https://regex101.com/r/uXSwLc/1
|
||||
regex: '(?i)(span(-|_||)id("|=| |-|:)*)(?P<span_id>[A-Fa-f0-9]+)'
|
||||
parse_from: body
|
||||
parse_to: attributes.temp_trace
|
||||
if: 'body matches "(?i)(span(-|_||)id(\"|=| |-|:)*)(?P<span_id>[A-Fa-f0-9]+)"'
|
||||
output: trace_parser
|
||||
- type: trace_parser
|
||||
if: '"trace_id" in attributes or "span_id" in attributes'
|
||||
id: trace_parser
|
||||
trace_id:
|
||||
parse_from: attributes.trace_id
|
||||
parse_from: attributes.temp_trace.trace_id
|
||||
span_id:
|
||||
parse_from: attributes.span_id
|
||||
output: remove_trace_id
|
||||
- type: trace_parser
|
||||
if: '"traceId" in attributes or "spanId" in attributes'
|
||||
trace_id:
|
||||
parse_from: attributes.traceId
|
||||
span_id:
|
||||
parse_from: attributes.spanId
|
||||
output: remove_traceId
|
||||
- id: remove_traceId
|
||||
type: remove
|
||||
if: '"traceId" in attributes'
|
||||
field: attributes.traceId
|
||||
output: remove_spanId
|
||||
- id: remove_spanId
|
||||
type: remove
|
||||
if: '"spanId" in attributes'
|
||||
field: attributes.spanId
|
||||
- id: remove_trace_id
|
||||
type: remove
|
||||
if: '"trace_id" in attributes'
|
||||
field: attributes.trace_id
|
||||
output: remove_span_id
|
||||
- id: remove_span_id
|
||||
type: remove
|
||||
if: '"span_id" in attributes'
|
||||
field: attributes.span_id
|
||||
parse_from: attributes.temp_trace.span_id
|
||||
output: remove_temp
|
||||
- type: remove
|
||||
id: remove_temp
|
||||
field: attributes.temp_trace
|
||||
if: '"temp_trace" in attributes'
|
||||
batch:
|
||||
send_batch_size: 10000
|
||||
send_batch_max_size: 11000
|
||||
|
@ -66,7 +66,7 @@ services:
|
||||
- --storage.path=/data
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.79.12}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.79.13}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@ -81,7 +81,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
otel-collector:
|
||||
container_name: signoz-otel-collector
|
||||
image: signoz/signoz-otel-collector:0.79.12
|
||||
image: signoz/signoz-otel-collector:0.79.13
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@ -118,7 +118,7 @@ services:
|
||||
|
||||
otel-collector-metrics:
|
||||
container_name: signoz-otel-collector-metrics
|
||||
image: signoz/signoz-otel-collector:0.79.12
|
||||
image: signoz/signoz-otel-collector:0.79.13
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
@ -164,7 +164,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.32.1}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.33.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@ -203,7 +203,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.32.1}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.33.0}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@ -215,7 +215,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.79.12}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.79.13}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@ -229,7 +229,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.12}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.13}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
@ -269,7 +269,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.12}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.13}
|
||||
container_name: signoz-otel-collector-metrics
|
||||
command:
|
||||
[
|
||||
|
@ -64,38 +64,33 @@ receivers:
|
||||
processors:
|
||||
logstransform/internal:
|
||||
operators:
|
||||
- type: regex_parser
|
||||
id: traceid
|
||||
# https://regex101.com/r/MMfNjk/1
|
||||
regex: '(?i)(trace(-|_||)id("|=| |-|:)*)(?P<trace_id>[A-Fa-f0-9]+)'
|
||||
parse_from: body
|
||||
parse_to: attributes.temp_trace
|
||||
if: 'body matches "(?i)(trace(-|_||)id(\"|=| |-|:)*)(?P<trace_id>[A-Fa-f0-9]+)"'
|
||||
output: spanid
|
||||
- type: regex_parser
|
||||
id: spanid
|
||||
# https://regex101.com/r/uXSwLc/1
|
||||
regex: '(?i)(span(-|_||)id("|=| |-|:)*)(?P<span_id>[A-Fa-f0-9]+)'
|
||||
parse_from: body
|
||||
parse_to: attributes.temp_trace
|
||||
if: 'body matches "(?i)(span(-|_||)id(\"|=| |-|:)*)(?P<span_id>[A-Fa-f0-9]+)"'
|
||||
output: trace_parser
|
||||
- type: trace_parser
|
||||
if: '"trace_id" in attributes or "span_id" in attributes'
|
||||
id: trace_parser
|
||||
trace_id:
|
||||
parse_from: attributes.trace_id
|
||||
parse_from: attributes.temp_trace.trace_id
|
||||
span_id:
|
||||
parse_from: attributes.span_id
|
||||
output: remove_trace_id
|
||||
- type: trace_parser
|
||||
if: '"traceId" in attributes or "spanId" in attributes'
|
||||
trace_id:
|
||||
parse_from: attributes.traceId
|
||||
span_id:
|
||||
parse_from: attributes.spanId
|
||||
output: remove_traceId
|
||||
- id: remove_traceId
|
||||
type: remove
|
||||
if: '"traceId" in attributes'
|
||||
field: attributes.traceId
|
||||
output: remove_spanId
|
||||
- id: remove_spanId
|
||||
type: remove
|
||||
if: '"spanId" in attributes'
|
||||
field: attributes.spanId
|
||||
- id: remove_trace_id
|
||||
type: remove
|
||||
if: '"trace_id" in attributes'
|
||||
field: attributes.trace_id
|
||||
output: remove_span_id
|
||||
- id: remove_span_id
|
||||
type: remove
|
||||
if: '"span_id" in attributes'
|
||||
field: attributes.span_id
|
||||
parse_from: attributes.temp_trace.span_id
|
||||
output: remove_temp
|
||||
- type: remove
|
||||
id: remove_temp
|
||||
field: attributes.temp_trace
|
||||
if: '"temp_trace" in attributes'
|
||||
batch:
|
||||
send_batch_size: 10000
|
||||
send_batch_max_size: 11000
|
||||
|
@ -1,5 +1,5 @@
|
||||
# use a minimal alpine image
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.18.3
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
|
@ -37,9 +37,9 @@
|
||||
"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_trace_id_placeholder": "Parse Trace ID from",
|
||||
"processor_span_id_placeholder": "Parse Span ID from",
|
||||
"processor_trace_flags_placeholder": "Parse Trace flags from",
|
||||
"processor_from_placeholder": "From",
|
||||
"processor_to_placeholder": "To"
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||
"LOGS_PIPELINES": "SigNoz | Logs Pipelines",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
|
@ -39,10 +39,12 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const { data: licensesData } = useLicense();
|
||||
const {
|
||||
data: licensesData,
|
||||
isFetching: isFetchingLicensesData,
|
||||
} = useLicense();
|
||||
|
||||
const {
|
||||
user,
|
||||
isUserFetching,
|
||||
isUserFetchingError,
|
||||
isLoggedIn: isLoggedInState,
|
||||
@ -116,7 +118,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
if (
|
||||
localStorageUserAuthToken &&
|
||||
localStorageUserAuthToken.refreshJwt &&
|
||||
user?.userId === ''
|
||||
isUserFetching
|
||||
) {
|
||||
handleUserLoginIfTokenPresent(key);
|
||||
} else {
|
||||
@ -131,28 +133,34 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
if (path && path !== ROUTES.WORKSPACE_LOCKED) {
|
||||
history.push(ROUTES.WORKSPACE_LOCKED);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
payload: {
|
||||
isUserFetching: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetchingLicensesData) {
|
||||
const shouldBlockWorkspace = licensesData?.payload?.workSpaceBlock;
|
||||
|
||||
if (shouldBlockWorkspace) {
|
||||
navigateToWorkSpaceBlocked(currentRoute);
|
||||
}
|
||||
}
|
||||
}, [isFetchingLicensesData]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
useEffect(() => {
|
||||
(async (): Promise<void> => {
|
||||
try {
|
||||
const shouldBlockWorkspace = licensesData?.payload?.workSpaceBlock;
|
||||
|
||||
if (currentRoute) {
|
||||
const { isPrivate, key } = currentRoute;
|
||||
|
||||
if (shouldBlockWorkspace) {
|
||||
navigateToWorkSpaceBlocked(currentRoute);
|
||||
} else if (isPrivate) {
|
||||
if (isPrivate && key !== ROUTES.WORKSPACE_LOCKED) {
|
||||
handlePrivateRoutes(key);
|
||||
} else {
|
||||
// no need to fetch the user and make user fetching false
|
||||
|
@ -13,6 +13,7 @@ import useLicense, { LICENSE_PLAN_KEY } from 'hooks/useLicense';
|
||||
import { NotificationProvider } from 'hooks/useNotifications';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import history from 'lib/history';
|
||||
import { identity, pickBy } from 'lodash-es';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
@ -90,13 +91,19 @@ function App(): JSX.Element {
|
||||
const orgName =
|
||||
org && Array.isArray(org) && org.length > 0 ? org[0].name : '';
|
||||
|
||||
const { name, email } = user;
|
||||
|
||||
const identifyPayload = {
|
||||
email: user?.email,
|
||||
name: user?.name,
|
||||
email,
|
||||
name,
|
||||
company_name: orgName,
|
||||
role,
|
||||
source: 'signoz-ui',
|
||||
};
|
||||
const domain = extractDomain(user?.email);
|
||||
|
||||
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
|
||||
|
||||
const domain = extractDomain(email);
|
||||
|
||||
const hostNameParts = hostname.split('.');
|
||||
|
||||
@ -106,13 +113,14 @@ function App(): JSX.Element {
|
||||
data_region: hostNameParts[1],
|
||||
tenant_url: hostname,
|
||||
company_domain: domain,
|
||||
source: 'signoz-ui',
|
||||
};
|
||||
|
||||
window.analytics.identify(user?.email, identifyPayload);
|
||||
window.analytics.identify(email, sanitizedIdentifyPayload);
|
||||
|
||||
window.analytics.group(domain, groupTraits);
|
||||
|
||||
window.clarity('identify', user.email, user.name);
|
||||
window.clarity('identify', email, name);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -282,10 +282,10 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
path: ROUTES.PIPELINES,
|
||||
path: ROUTES.LOGS_PIPELINES,
|
||||
exact: true,
|
||||
component: PipelinePage,
|
||||
key: 'PIPELINES',
|
||||
key: 'LOGS_PIPELINES',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
@ -299,7 +299,7 @@ const routes: AppRoutes[] = [
|
||||
path: ROUTES.WORKSPACE_LOCKED,
|
||||
exact: true,
|
||||
component: WorkspaceBlocked,
|
||||
isPrivate: false,
|
||||
isPrivate: true,
|
||||
key: 'WORKSPACE_LOCKED',
|
||||
},
|
||||
];
|
||||
|
11
frontend/src/components/DropDown/DropDown.styles.scss
Normal file
11
frontend/src/components/DropDown/DropDown.styles.scss
Normal file
@ -0,0 +1,11 @@
|
||||
.dropdown-button {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dropdown-button--dark {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
30
frontend/src/components/DropDown/DropDown.tsx
Normal file
30
frontend/src/components/DropDown/DropDown.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import './DropDown.styles.scss';
|
||||
|
||||
import { EllipsisOutlined } from '@ant-design/icons';
|
||||
import { Button, Dropdown, MenuProps } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
|
||||
function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const items: MenuProps['items'] = element.map(
|
||||
(e: JSX.Element, index: number) => ({
|
||||
label: e,
|
||||
key: index,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown menu={{ items }}>
|
||||
<Button
|
||||
type="link"
|
||||
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
|
||||
onClick={(e): void => e.preventDefault()}
|
||||
>
|
||||
<EllipsisOutlined className="dropdown-icon" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropDown;
|
@ -0,0 +1,25 @@
|
||||
.DynamicColumnTable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.dynamicColumnTable-button {
|
||||
align-self: flex-end;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dynamicColumnsTable-items {
|
||||
display: flex;
|
||||
width: 10.625rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dynamicColumnsTable-items {
|
||||
flex-direction: column;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
112
frontend/src/components/ResizeTable/DynamicColumnTable.tsx
Normal file
112
frontend/src/components/ResizeTable/DynamicColumnTable.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import './DynamicColumnTable.syles.scss';
|
||||
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { Button, Dropdown, MenuProps, Switch } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import ResizeTable from './ResizeTable';
|
||||
import { DynamicColumnTableProps } from './types';
|
||||
import {
|
||||
getNewColumnData,
|
||||
getVisibleColumns,
|
||||
setVisibleColumns,
|
||||
} from './utils';
|
||||
|
||||
function DynamicColumnTable({
|
||||
tablesource,
|
||||
columns,
|
||||
dynamicColumns,
|
||||
onDragColumn,
|
||||
...restProps
|
||||
}: DynamicColumnTableProps): JSX.Element {
|
||||
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
|
||||
columns,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const visibleColumns = getVisibleColumns({
|
||||
tablesource,
|
||||
columnsData: columns,
|
||||
dynamicColumns,
|
||||
});
|
||||
setColumnsData((prevColumns) =>
|
||||
prevColumns
|
||||
? [
|
||||
...prevColumns.slice(0, prevColumns.length - 1),
|
||||
...visibleColumns,
|
||||
prevColumns[prevColumns.length - 1],
|
||||
]
|
||||
: undefined,
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onToggleHandler = (index: number) => (
|
||||
checked: boolean,
|
||||
event: React.MouseEvent<HTMLButtonElement>,
|
||||
): void => {
|
||||
event.stopPropagation();
|
||||
setVisibleColumns({
|
||||
tablesource,
|
||||
dynamicColumns,
|
||||
index,
|
||||
checked,
|
||||
});
|
||||
setColumnsData((prevColumns) =>
|
||||
getNewColumnData({
|
||||
checked,
|
||||
index,
|
||||
prevColumns,
|
||||
dynamicColumns,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const items: MenuProps['items'] =
|
||||
dynamicColumns?.map((column, index) => ({
|
||||
label: (
|
||||
<div className="dynamicColumnsTable-items">
|
||||
<div>{column.title?.toString()}</div>
|
||||
<Switch
|
||||
checked={columnsData?.findIndex((c) => c.key === column.key) !== -1}
|
||||
onChange={onToggleHandler(index)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
key: index,
|
||||
type: 'checkbox',
|
||||
})) || [];
|
||||
|
||||
return (
|
||||
<div className="DynamicColumnTable">
|
||||
{dynamicColumns && (
|
||||
<Dropdown
|
||||
getPopupContainer={popupContainer}
|
||||
menu={{ items }}
|
||||
trigger={['click']}
|
||||
>
|
||||
<Button
|
||||
className="dynamicColumnTable-button"
|
||||
size="middle"
|
||||
icon={<SettingOutlined />}
|
||||
/>
|
||||
</Dropdown>
|
||||
)}
|
||||
|
||||
<ResizeTable
|
||||
columns={columnsData}
|
||||
onDragColumn={onDragColumn}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DynamicColumnTable.defaultProps = {
|
||||
onDragColumn: undefined,
|
||||
};
|
||||
|
||||
export default memo(DynamicColumnTable);
|
@ -0,0 +1,15 @@
|
||||
import { Typography } from 'antd';
|
||||
|
||||
import Time from './Time';
|
||||
|
||||
function DateComponent(
|
||||
CreatedOrUpdateTime: string | number | Date,
|
||||
): JSX.Element {
|
||||
if (CreatedOrUpdateTime === null) {
|
||||
return <Typography> - </Typography>;
|
||||
}
|
||||
|
||||
return <Time CreatedOrUpdateTime={CreatedOrUpdateTime} />;
|
||||
}
|
||||
|
||||
export default DateComponent;
|
@ -2,16 +2,15 @@ import { Typography } from 'antd';
|
||||
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||
import getFormattedDate from 'lib/getFormatedDate';
|
||||
|
||||
import { Data } from '..';
|
||||
|
||||
function DateComponent(lastUpdatedTime: Data['lastUpdatedTime']): JSX.Element {
|
||||
const time = new Date(lastUpdatedTime);
|
||||
|
||||
function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element {
|
||||
const time = new Date(CreatedOrUpdateTime);
|
||||
const date = getFormattedDate(time);
|
||||
|
||||
const timeString = `${date} ${convertDateToAmAndPm(time)}`;
|
||||
|
||||
return <Typography>{timeString}</Typography>;
|
||||
}
|
||||
|
||||
export default DateComponent;
|
||||
type DateProps = {
|
||||
CreatedOrUpdateTime: string | number | Date;
|
||||
};
|
||||
|
||||
export default Time;
|
11
frontend/src/components/ResizeTable/contants.ts
Normal file
11
frontend/src/components/ResizeTable/contants.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export const TableDataSource = {
|
||||
Alert: 'alert',
|
||||
Dashboard: 'dashboard',
|
||||
} as const;
|
||||
|
||||
export const DynamicColumnsKey = {
|
||||
CreatedAt: 'createdAt',
|
||||
CreatedBy: 'createdBy',
|
||||
UpdatedAt: 'updatedAt',
|
||||
UpdatedBy: 'updatedBy',
|
||||
};
|
@ -1,6 +1,43 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { TableProps } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table';
|
||||
|
||||
import { TableDataSource } from './contants';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface ResizeTableProps extends TableProps<any> {
|
||||
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
||||
}
|
||||
export interface DynamicColumnTableProps extends TableProps<any> {
|
||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||
dynamicColumns: TableProps<any>['columns'];
|
||||
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
||||
}
|
||||
|
||||
export type GetVisibleColumnsFunction = (
|
||||
props: GetVisibleColumnProps,
|
||||
) => (ColumnGroupType<any> | ColumnType<any>)[];
|
||||
|
||||
export type GetVisibleColumnProps = {
|
||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||
dynamicColumns?: ColumnsType<any>;
|
||||
columnsData?: ColumnsType;
|
||||
};
|
||||
|
||||
export type SetVisibleColumnsProps = {
|
||||
checked: boolean;
|
||||
index: number;
|
||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||
dynamicColumns?: ColumnsType<any>;
|
||||
};
|
||||
|
||||
type GetNewColumnDataProps = {
|
||||
prevColumns?: ColumnsType;
|
||||
checked: boolean;
|
||||
dynamicColumns?: ColumnsType<any>;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export type GetNewColumnDataFunction = (
|
||||
props: GetNewColumnDataProps,
|
||||
) => ColumnsType | undefined;
|
||||
|
77
frontend/src/components/ResizeTable/utils.ts
Normal file
77
frontend/src/components/ResizeTable/utils.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { DynamicColumnsKey } from './contants';
|
||||
import {
|
||||
GetNewColumnDataFunction,
|
||||
GetVisibleColumnsFunction,
|
||||
SetVisibleColumnsProps,
|
||||
} from './types';
|
||||
|
||||
export const getVisibleColumns: GetVisibleColumnsFunction = ({
|
||||
tablesource,
|
||||
dynamicColumns,
|
||||
columnsData,
|
||||
}) => {
|
||||
let columnVisibilityData: { [key: string]: boolean };
|
||||
try {
|
||||
const storedData = localStorage.getItem(tablesource);
|
||||
if (typeof storedData === 'string' && dynamicColumns) {
|
||||
columnVisibilityData = JSON.parse(storedData);
|
||||
return dynamicColumns.filter((column) => {
|
||||
if (column.key && !columnsData?.find((c) => c.key === column.key)) {
|
||||
return columnVisibilityData[column.key];
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
const initialColumnVisibility: Record<string, boolean> = {};
|
||||
Object.values(DynamicColumnsKey).forEach((key) => {
|
||||
initialColumnVisibility[key] = false;
|
||||
});
|
||||
|
||||
localStorage.setItem(tablesource, JSON.stringify(initialColumnVisibility));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const setVisibleColumns = ({
|
||||
checked,
|
||||
index,
|
||||
tablesource,
|
||||
dynamicColumns,
|
||||
}: SetVisibleColumnsProps): void => {
|
||||
try {
|
||||
const storedData = localStorage.getItem(tablesource);
|
||||
if (typeof storedData === 'string' && dynamicColumns) {
|
||||
const columnVisibilityData = JSON.parse(storedData);
|
||||
const { key } = dynamicColumns[index];
|
||||
if (key) {
|
||||
columnVisibilityData[key] = checked;
|
||||
}
|
||||
localStorage.setItem(tablesource, JSON.stringify(columnVisibilityData));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getNewColumnData: GetNewColumnDataFunction = ({
|
||||
prevColumns,
|
||||
checked,
|
||||
dynamicColumns,
|
||||
index,
|
||||
}) => {
|
||||
if (checked && dynamicColumns) {
|
||||
return prevColumns
|
||||
? [
|
||||
...prevColumns.slice(0, prevColumns.length - 1),
|
||||
dynamicColumns[index],
|
||||
prevColumns[prevColumns.length - 1],
|
||||
]
|
||||
: undefined;
|
||||
}
|
||||
return prevColumns && dynamicColumns
|
||||
? prevColumns.filter((column) => dynamicColumns[index].title !== column.title)
|
||||
: undefined;
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
.label-column {
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.label-column--tag {
|
||||
white-space: normal;
|
||||
margin: 0.2rem 0.2rem;
|
||||
}
|
||||
}
|
54
frontend/src/components/TableRenderer/LabelColumn.tsx
Normal file
54
frontend/src/components/TableRenderer/LabelColumn.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import './LabelColumn.styles.scss';
|
||||
|
||||
import { Popover, Tag } from 'antd';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { LabelColumnProps } from './TableRenderer.types';
|
||||
import TagWithToolTip from './TagWithToolTip';
|
||||
|
||||
function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
|
||||
const newLabels = labels.length > 3 ? labels.slice(0, 3) : labels;
|
||||
const remainingLabels = labels.length > 3 ? labels.slice(3) : [];
|
||||
|
||||
return (
|
||||
<div className="label-column">
|
||||
{newLabels.map(
|
||||
(label: string): JSX.Element => (
|
||||
<TagWithToolTip key={label} label={label} color={color} value={value} />
|
||||
),
|
||||
)}
|
||||
{remainingLabels.length > 0 && (
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomRight"
|
||||
showArrow={false}
|
||||
content={
|
||||
<div>
|
||||
{labels.map(
|
||||
(label: string): JSX.Element => (
|
||||
<TagWithToolTip
|
||||
key={label}
|
||||
label={label}
|
||||
color={color}
|
||||
value={value}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
trigger="hover"
|
||||
>
|
||||
<Tag className="label-column--tag" color={color}>
|
||||
+{remainingLabels.length}
|
||||
</Tag>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LabelColumn.defaultProps = {
|
||||
value: {},
|
||||
};
|
||||
|
||||
export default LabelColumn;
|
@ -0,0 +1,5 @@
|
||||
export type LabelColumnProps = {
|
||||
labels: string[];
|
||||
color?: string;
|
||||
value?: { [key: string]: string };
|
||||
};
|
36
frontend/src/components/TableRenderer/TagWithToolTip.tsx
Normal file
36
frontend/src/components/TableRenderer/TagWithToolTip.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
|
||||
import { getLabelRenderingValue } from './utils';
|
||||
|
||||
function TagWithToolTip({
|
||||
label,
|
||||
value,
|
||||
color,
|
||||
}: TagWithToolTipProps): JSX.Element {
|
||||
const tooltipTitle =
|
||||
value && value[label] ? `${label}: ${value[label]}` : label;
|
||||
return (
|
||||
<div key={label}>
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<Tag className="label-column--tag" color={color}>
|
||||
{getLabelRenderingValue(label, value && value[label])}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type TagWithToolTipProps = {
|
||||
label: string;
|
||||
color?: string;
|
||||
value?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
|
||||
TagWithToolTip.defaultProps = {
|
||||
value: undefined,
|
||||
color: undefined,
|
||||
};
|
||||
|
||||
export default TagWithToolTip;
|
@ -16,6 +16,28 @@ export const generatorResizeTableColumns = <T>({
|
||||
};
|
||||
});
|
||||
|
||||
export const getLabelRenderingValue = (
|
||||
label: string,
|
||||
value?: string,
|
||||
): string => {
|
||||
const maxLength = 20;
|
||||
|
||||
if (label.length > maxLength) {
|
||||
return `${label.slice(0, maxLength)}...`;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
const remainingSpace = maxLength - label.length;
|
||||
let newValue = value;
|
||||
if (value.length > remainingSpace) {
|
||||
newValue = `${value.slice(0, remainingSpace)}...`;
|
||||
}
|
||||
return `${label}: ${newValue}`;
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
interface GeneratorResizeTableColumnsProp<T> {
|
||||
baseColumnOptions: ColumnsType<T>;
|
||||
dynamicColumnOption: { key: string; columnOption: ColumnType<T> }[];
|
||||
|
@ -31,13 +31,12 @@ const ROUTES = {
|
||||
LOGS: '/logs',
|
||||
LOGS_EXPLORER: '/logs-explorer',
|
||||
LIVE_LOGS: '/logs-explorer/live',
|
||||
LOGS_PIPELINES: '/pipelines',
|
||||
HOME_PAGE: '/',
|
||||
PASSWORD_RESET: '/password-reset',
|
||||
LIST_LICENSES: '/licenses',
|
||||
LOGS_INDEX_FIELDS: '/logs-explorer/index-fields',
|
||||
LOGS_PIPELINE: '/logs-explorer/pipeline',
|
||||
TRACE_EXPLORER: '/trace-explorer',
|
||||
PIPELINES: '/pipelines',
|
||||
BILLING: '/billing',
|
||||
SUPPORT: '/support',
|
||||
WORKSPACE_LOCKED: '/workspace-locked',
|
||||
|
4
frontend/src/container/Download/Download.styles.scss
Normal file
4
frontend/src/container/Download/Download.styles.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.download-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
77
frontend/src/container/Download/Download.tsx
Normal file
77
frontend/src/container/Download/Download.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import './Download.styles.scss';
|
||||
|
||||
import { CloudDownloadOutlined } from '@ant-design/icons';
|
||||
import { Button, Dropdown, MenuProps } from 'antd';
|
||||
import { Excel } from 'antd-table-saveas-excel';
|
||||
import { unparse } from 'papaparse';
|
||||
|
||||
import { DownloadProps } from './Download.types';
|
||||
|
||||
function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
|
||||
const downloadExcelFile = (): void => {
|
||||
const headers = Object.keys(Object.assign({}, ...data)).map((item) => {
|
||||
const updatedTitle = item
|
||||
.split('_')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
return {
|
||||
title: updatedTitle,
|
||||
dataIndex: item,
|
||||
};
|
||||
});
|
||||
const excel = new Excel();
|
||||
excel
|
||||
.addSheet(fileName)
|
||||
.addColumns(headers)
|
||||
.addDataSource(data, {
|
||||
str2Percent: true,
|
||||
})
|
||||
.saveAs(`${fileName}.xlsx`);
|
||||
};
|
||||
|
||||
const downloadCsvFile = (): void => {
|
||||
const csv = unparse(data);
|
||||
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const csvUrl = URL.createObjectURL(csvBlob);
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = csvUrl;
|
||||
downloadLink.download = `${fileName}.csv`;
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
};
|
||||
|
||||
const menu: MenuProps = {
|
||||
items: [
|
||||
{
|
||||
key: 'download-as-excel',
|
||||
label: 'Excel',
|
||||
onClick: downloadExcelFile,
|
||||
},
|
||||
{
|
||||
key: 'download-as-csv',
|
||||
label: 'CSV',
|
||||
onClick: downloadCsvFile,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown menu={menu} trigger={['click']}>
|
||||
<Button
|
||||
className="download-button"
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
>
|
||||
<CloudDownloadOutlined />
|
||||
Download
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
Download.defaultProps = {
|
||||
isLoading: undefined,
|
||||
};
|
||||
|
||||
export default Download;
|
10
frontend/src/container/Download/Download.types.ts
Normal file
10
frontend/src/container/Download/Download.types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export type DownloadOptions = {
|
||||
isDownloadEnabled: boolean;
|
||||
fileName: string;
|
||||
};
|
||||
|
||||
export type DownloadProps = {
|
||||
data: Record<string, string>[];
|
||||
isLoading?: boolean;
|
||||
fileName: string;
|
||||
};
|
@ -2,6 +2,7 @@ import { Form, Select } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertDef, Labels } from 'types/api/alerts/def';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import ChannelSelect from './ChannelSelect';
|
||||
import LabelSelect from './labels';
|
||||
@ -36,6 +37,7 @@ function BasicInfo({ alertDef, setAlertDef }: BasicInfoProps): JSX.Element {
|
||||
name={['labels', 'severity']}
|
||||
>
|
||||
<SeveritySelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue="critical"
|
||||
onChange={(value: unknown | string): void => {
|
||||
const s = (value as string) || 'critical';
|
||||
|
@ -9,9 +9,12 @@ import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { AlertDef } from 'types/api/alerts/def';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { ChartContainer, FailedMessageContainer } from './styles';
|
||||
import { covertIntoDataFormats } from './utils';
|
||||
@ -41,6 +44,9 @@ function ChartPreview({
|
||||
}: ChartPreviewProps): JSX.Element | null {
|
||||
const { t } = useTranslation('alerts');
|
||||
const threshold = alertDef?.condition.target || 0;
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const thresholdValue = covertIntoDataFormats({
|
||||
value: threshold,
|
||||
@ -100,6 +106,8 @@ function ChartPreview({
|
||||
'chartPreview',
|
||||
userQueryKey || JSON.stringify(query),
|
||||
selectedInterval,
|
||||
minTime,
|
||||
maxTime,
|
||||
],
|
||||
retry: false,
|
||||
enabled: canQuery,
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
defaultMatchType,
|
||||
} from 'types/api/alerts/def';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { FormContainer, InlineSelect, StepHeading } from './styles';
|
||||
|
||||
@ -45,6 +46,7 @@ function RuleOptions({
|
||||
|
||||
const renderCompareOps = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue={defaultCompareOp}
|
||||
value={alertDef.condition?.op}
|
||||
style={{ minWidth: '120px' }}
|
||||
@ -69,6 +71,7 @@ function RuleOptions({
|
||||
|
||||
const renderThresholdMatchOpts = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue={defaultMatchType}
|
||||
style={{ minWidth: '130px' }}
|
||||
value={alertDef.condition?.matchType}
|
||||
@ -83,6 +86,7 @@ function RuleOptions({
|
||||
|
||||
const renderPromMatchOpts = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue={defaultMatchType}
|
||||
style={{ minWidth: '130px' }}
|
||||
value={alertDef.condition?.matchType}
|
||||
@ -94,6 +98,7 @@ function RuleOptions({
|
||||
|
||||
const renderEvalWindows = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue={defaultEvalWindow}
|
||||
style={{ minWidth: '120px' }}
|
||||
value={alertDef.evalWindow}
|
||||
@ -180,6 +185,7 @@ function RuleOptions({
|
||||
|
||||
<Form.Item>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={categorySelectOptions}
|
||||
|
@ -44,7 +44,6 @@ import {
|
||||
StyledLeftContainer,
|
||||
} from './styles';
|
||||
import UserGuide from './UserGuide';
|
||||
import { getUpdatedStepInterval, toChartInterval } from './utils';
|
||||
|
||||
function FormAlertRules({
|
||||
alertType,
|
||||
@ -55,9 +54,10 @@ function FormAlertRules({
|
||||
// init namespace for translations
|
||||
const { t } = useTranslation('alerts');
|
||||
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const {
|
||||
currentQuery,
|
||||
@ -354,16 +354,6 @@ function FormAlertRules({
|
||||
<BasicInfo alertDef={alertDef} setAlertDef={setAlertDef} />
|
||||
);
|
||||
|
||||
const updatedStagedQuery = useMemo((): Query | null => {
|
||||
const newQuery: Query | null = stagedQuery;
|
||||
if (newQuery) {
|
||||
newQuery.builder.queryData[0].stepInterval = getUpdatedStepInterval(
|
||||
alertDef.evalWindow,
|
||||
);
|
||||
}
|
||||
return newQuery;
|
||||
}, [alertDef.evalWindow, stagedQuery]);
|
||||
|
||||
const renderQBChartPreview = (): JSX.Element => (
|
||||
<ChartPreview
|
||||
headline={
|
||||
@ -373,10 +363,9 @@ function FormAlertRules({
|
||||
/>
|
||||
}
|
||||
name=""
|
||||
query={updatedStagedQuery}
|
||||
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
||||
query={stagedQuery}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
alertDef={alertDef}
|
||||
allowSelectedIntervalForStepGen
|
||||
/>
|
||||
);
|
||||
|
||||
@ -405,7 +394,7 @@ function FormAlertRules({
|
||||
name="Chart Preview"
|
||||
query={stagedQuery}
|
||||
alertDef={alertDef}
|
||||
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
||||
selectedInterval={globalSelectedInterval}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Typography } from 'antd';
|
||||
import { Skeleton, Typography } from 'antd';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||
@ -249,20 +249,23 @@ function WidgetGraphComponent({
|
||||
isWarning={isWarning}
|
||||
/>
|
||||
</div>
|
||||
<GridPanelSwitch
|
||||
panelType={widget.panelTypes}
|
||||
data={data}
|
||||
isStacked={widget.isStacked}
|
||||
opacity={widget.opacity}
|
||||
title={' '}
|
||||
name={name}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
onClickHandler={onClickHandler}
|
||||
onDragSelect={onDragSelect}
|
||||
panelData={queryResponse.data?.payload?.data.newResult.data.result || []}
|
||||
query={widget.query}
|
||||
ref={lineChartRef}
|
||||
/>
|
||||
{queryResponse.isLoading && <Skeleton />}
|
||||
{queryResponse.isSuccess && (
|
||||
<GridPanelSwitch
|
||||
panelType={widget.panelTypes}
|
||||
data={data}
|
||||
isStacked={widget.isStacked}
|
||||
opacity={widget.opacity}
|
||||
title={' '}
|
||||
name={name}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
onClickHandler={onClickHandler}
|
||||
onDragSelect={onDragSelect}
|
||||
panelData={queryResponse.data?.payload?.data.newResult.data.result || []}
|
||||
query={widget.query}
|
||||
ref={lineChartRef}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Skeleton } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||
@ -99,10 +98,6 @@ function GridCardGraph({
|
||||
|
||||
const isEmptyLayout = widget?.id === PANEL_TYPES.EMPTY_WIDGET;
|
||||
|
||||
if (queryResponse.isLoading) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<span ref={graphRef}>
|
||||
<WidgetGraphComponent
|
||||
|
@ -3,7 +3,14 @@ import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import DropDown from 'components/DropDown/DropDown';
|
||||
import {
|
||||
DynamicColumnsKey,
|
||||
TableDataSource,
|
||||
} from 'components/ResizeTable/contants';
|
||||
import DynamicColumnTable from 'components/ResizeTable/DynamicColumnTable';
|
||||
import DateComponent from 'components/ResizeTable/TableComponent/DateComponent';
|
||||
import LabelColumn from 'components/TableRenderer/LabelColumn';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -22,7 +29,7 @@ import { GettableAlert } from 'types/api/alerts/get';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import DeleteAlert from './DeleteAlert';
|
||||
import { Button, ButtonContainer, ColumnButton, StyledTag } from './styles';
|
||||
import { Button, ButtonContainer, ColumnButton } from './styles';
|
||||
import Status from './TableComponents/Status';
|
||||
import ToggleAlertState from './ToggleAlertState';
|
||||
|
||||
@ -121,6 +128,51 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const dynamicColumns: ColumnsType<GettableAlert> = [
|
||||
{
|
||||
title: 'Created At',
|
||||
dataIndex: 'createAt',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.CreatedAt,
|
||||
align: 'center',
|
||||
sorter: (a: GettableAlert, b: GettableAlert): number => {
|
||||
const prev = new Date(a.createAt).getTime();
|
||||
const next = new Date(b.createAt).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
render: DateComponent,
|
||||
},
|
||||
{
|
||||
title: 'Created By',
|
||||
dataIndex: 'createBy',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.CreatedBy,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: 'Updated At',
|
||||
dataIndex: 'updateAt',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.UpdatedAt,
|
||||
align: 'center',
|
||||
sorter: (a: GettableAlert, b: GettableAlert): number => {
|
||||
const prev = new Date(a.updateAt).getTime();
|
||||
const next = new Date(b.updateAt).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
render: DateComponent,
|
||||
},
|
||||
{
|
||||
title: 'Updated By',
|
||||
dataIndex: 'updateBy',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.UpdatedBy,
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
const columns: ColumnsType<GettableAlert> = [
|
||||
{
|
||||
title: 'Status',
|
||||
@ -178,13 +230,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{withOutSeverityKeys.map((e) => (
|
||||
<StyledTag key={e} color="magenta">
|
||||
{e}: {value[e]}
|
||||
</StyledTag>
|
||||
))}
|
||||
</>
|
||||
<LabelColumn labels={withOutSeverityKeys} value={value} color="magenta" />
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -195,20 +241,30 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
title: 'Action',
|
||||
dataIndex: 'id',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
width: 10,
|
||||
render: (id: GettableAlert['id'], record): JSX.Element => (
|
||||
<>
|
||||
<ToggleAlertState disabled={record.disabled} setData={setData} id={id} />
|
||||
|
||||
<ColumnButton onClick={onEditHandler(record)} type="link">
|
||||
Edit
|
||||
</ColumnButton>
|
||||
<ColumnButton onClick={onCloneHandler(record)} type="link">
|
||||
Clone
|
||||
</ColumnButton>
|
||||
|
||||
<DeleteAlert notifications={notificationsApi} setData={setData} id={id} />
|
||||
</>
|
||||
<DropDown
|
||||
element={[
|
||||
<ToggleAlertState
|
||||
key="1"
|
||||
disabled={record.disabled}
|
||||
setData={setData}
|
||||
id={id}
|
||||
/>,
|
||||
<ColumnButton key="2" onClick={onEditHandler(record)} type="link">
|
||||
Edit
|
||||
</ColumnButton>,
|
||||
<ColumnButton key="3" onClick={onCloneHandler(record)} type="link">
|
||||
Clone
|
||||
</ColumnButton>,
|
||||
<DeleteAlert
|
||||
key="4"
|
||||
notifications={notificationsApi}
|
||||
setData={setData}
|
||||
id={id}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
@ -229,7 +285,13 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
</Button>
|
||||
)}
|
||||
</ButtonContainer>
|
||||
<ResizeTable columns={columns} rowKey="id" dataSource={data} />
|
||||
<DynamicColumnTable
|
||||
tablesource={TableDataSource.Alert}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
dynamicColumns={dynamicColumns}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button as ButtonComponent, Tag } from 'antd';
|
||||
import { Button as ButtonComponent } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
@ -23,9 +23,3 @@ export const ColumnButton = styled(ButtonComponent)`
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledTag = styled(Tag)`
|
||||
&&& {
|
||||
white-space: normal;
|
||||
}
|
||||
`;
|
||||
|
@ -10,6 +10,8 @@ describe('executeSearchQueries', () => {
|
||||
uuid: uuid(),
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
created_by: '',
|
||||
updated_by: '',
|
||||
data: {
|
||||
title: 'first dashboard',
|
||||
variables: {},
|
||||
@ -20,6 +22,8 @@ describe('executeSearchQueries', () => {
|
||||
uuid: uuid(),
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
created_by: '',
|
||||
updated_by: '',
|
||||
data: {
|
||||
title: 'second dashboard',
|
||||
variables: {},
|
||||
@ -30,6 +34,8 @@ describe('executeSearchQueries', () => {
|
||||
uuid: uuid(),
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
created_by: '',
|
||||
updated_by: '',
|
||||
data: {
|
||||
title: 'third dashboard (with special characters +?\\)',
|
||||
variables: {},
|
||||
|
@ -45,18 +45,30 @@ function DeleteButton({ id }: Data): JSX.Element {
|
||||
|
||||
// This is to avoid the type collision
|
||||
function Wrapper(props: Data): JSX.Element {
|
||||
const { createdBy, description, id, key, lastUpdatedTime, name, tags } = props;
|
||||
const {
|
||||
createdAt,
|
||||
description,
|
||||
id,
|
||||
key,
|
||||
lastUpdatedTime,
|
||||
name,
|
||||
tags,
|
||||
createdBy,
|
||||
lastUpdatedBy,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<DeleteButton
|
||||
{...{
|
||||
createdBy,
|
||||
createdAt,
|
||||
description,
|
||||
id,
|
||||
key,
|
||||
lastUpdatedTime,
|
||||
name,
|
||||
tags,
|
||||
createdBy,
|
||||
lastUpdatedBy,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -10,7 +10,12 @@ import {
|
||||
import { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import {
|
||||
DynamicColumnsKey,
|
||||
TableDataSource,
|
||||
} from 'components/ResizeTable/contants';
|
||||
import DynamicColumnTable from 'components/ResizeTable/DynamicColumnTable';
|
||||
import LabelColumn from 'components/TableRenderer/LabelColumn';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import SearchFilter from 'container/ListOfDashboard/SearchFilter';
|
||||
@ -26,13 +31,11 @@ import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import DateComponent from '../../components/ResizeTable/TableComponent/DateComponent';
|
||||
import ImportJSON from './ImportJSON';
|
||||
import { ButtonContainer, NewDashboardButton, TableContainer } from './styles';
|
||||
import Createdby from './TableComponents/CreatedBy';
|
||||
import DateComponent from './TableComponents/Date';
|
||||
import DeleteButton from './TableComponents/DeleteButton';
|
||||
import Name from './TableComponents/Name';
|
||||
import Tags from './TableComponents/Tags';
|
||||
|
||||
function ListOfAllDashboard(): JSX.Element {
|
||||
const {
|
||||
@ -71,48 +74,66 @@ function ListOfAllDashboard(): JSX.Element {
|
||||
errorMessage: '',
|
||||
});
|
||||
|
||||
const dynamicColumns: TableColumnProps<Data>[] = [
|
||||
{
|
||||
title: 'Created At',
|
||||
dataIndex: 'createdAt',
|
||||
width: 30,
|
||||
key: DynamicColumnsKey.CreatedAt,
|
||||
sorter: (a: Data, b: Data): number => {
|
||||
console.log({ a });
|
||||
const prev = new Date(a.createdAt).getTime();
|
||||
const next = new Date(b.createdAt).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
render: DateComponent,
|
||||
},
|
||||
{
|
||||
title: 'Created By',
|
||||
dataIndex: 'createdBy',
|
||||
width: 30,
|
||||
key: DynamicColumnsKey.CreatedBy,
|
||||
},
|
||||
{
|
||||
title: 'Last Updated Time',
|
||||
width: 30,
|
||||
dataIndex: 'lastUpdatedTime',
|
||||
key: DynamicColumnsKey.UpdatedAt,
|
||||
sorter: (a: Data, b: Data): number => {
|
||||
const prev = new Date(a.lastUpdatedTime).getTime();
|
||||
const next = new Date(b.lastUpdatedTime).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
render: DateComponent,
|
||||
},
|
||||
{
|
||||
title: 'Last Updated By',
|
||||
dataIndex: 'lastUpdatedBy',
|
||||
width: 30,
|
||||
key: DynamicColumnsKey.UpdatedBy,
|
||||
},
|
||||
];
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const tableColumns: TableColumnProps<Data>[] = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
width: 100,
|
||||
width: 40,
|
||||
render: Name,
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
width: 100,
|
||||
width: 50,
|
||||
dataIndex: 'description',
|
||||
},
|
||||
{
|
||||
title: 'Tags (can be multiple)',
|
||||
dataIndex: 'tags',
|
||||
width: 80,
|
||||
render: Tags,
|
||||
},
|
||||
{
|
||||
title: 'Created At',
|
||||
dataIndex: 'createdBy',
|
||||
width: 80,
|
||||
sorter: (a: Data, b: Data): number => {
|
||||
const prev = new Date(a.createdBy).getTime();
|
||||
const next = new Date(b.createdBy).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
render: Createdby,
|
||||
},
|
||||
{
|
||||
title: 'Last Updated Time',
|
||||
width: 90,
|
||||
dataIndex: 'lastUpdatedTime',
|
||||
sorter: (a: Data, b: Data): number => {
|
||||
const prev = new Date(a.lastUpdatedTime).getTime();
|
||||
const next = new Date(b.lastUpdatedTime).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
render: DateComponent,
|
||||
width: 50,
|
||||
render: (value): JSX.Element => <LabelColumn labels={value} />,
|
||||
},
|
||||
];
|
||||
|
||||
@ -130,13 +151,15 @@ function ListOfAllDashboard(): JSX.Element {
|
||||
|
||||
const data: Data[] =
|
||||
filteredDashboards?.map((e) => ({
|
||||
createdBy: e.created_at,
|
||||
createdAt: e.created_at,
|
||||
description: e.data.description || '',
|
||||
id: e.uuid,
|
||||
lastUpdatedTime: e.updated_at,
|
||||
name: e.data.title,
|
||||
tags: e.data.tags || [],
|
||||
key: e.uuid,
|
||||
createdBy: e.created_by,
|
||||
lastUpdatedBy: e.updated_by,
|
||||
refetchDashboardList,
|
||||
})) || [];
|
||||
|
||||
@ -290,7 +313,9 @@ function ListOfAllDashboard(): JSX.Element {
|
||||
uploadedGrafana={uploadedGrafana}
|
||||
onModalHandler={(): void => onModalHandler(false)}
|
||||
/>
|
||||
<ResizeTable
|
||||
<DynamicColumnTable
|
||||
tablesource={TableDataSource.Dashboard}
|
||||
dynamicColumns={dynamicColumns}
|
||||
columns={columns}
|
||||
pagination={{
|
||||
pageSize: 9,
|
||||
@ -314,7 +339,9 @@ export interface Data {
|
||||
description: string;
|
||||
tags: string[];
|
||||
createdBy: string;
|
||||
createdAt: string;
|
||||
lastUpdatedTime: string;
|
||||
lastUpdatedBy: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { CloudDownloadOutlined, FastBackwardOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Dropdown, MenuProps } from 'antd';
|
||||
import { Excel } from 'antd-table-saveas-excel';
|
||||
import { FastBackwardOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider } from 'antd';
|
||||
import Controls from 'container/Controls';
|
||||
import Download from 'container/Download/Download';
|
||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||
import dayjs from 'dayjs';
|
||||
import { Pagination } from 'hooks/queryPagination';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { OrderPreferenceItems } from 'pages/Logs/config';
|
||||
import { unparse } from 'papaparse';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@ -23,7 +22,7 @@ import {
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { Container, DownloadLogButton } from './styles';
|
||||
import { Container } from './styles';
|
||||
|
||||
function LogControls(): JSX.Element | null {
|
||||
const {
|
||||
@ -97,58 +96,6 @@ function LogControls(): JSX.Element | null {
|
||||
[logs],
|
||||
);
|
||||
|
||||
const downloadExcelFile = useCallback((): void => {
|
||||
const headers = Object.keys(Object.assign({}, ...flattenLogData)).map(
|
||||
(item) => {
|
||||
const updatedTitle = item
|
||||
.split('_')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
return {
|
||||
title: updatedTitle,
|
||||
dataIndex: item,
|
||||
};
|
||||
},
|
||||
);
|
||||
const excel = new Excel();
|
||||
excel
|
||||
.addSheet('log_data')
|
||||
.addColumns(headers)
|
||||
.addDataSource(flattenLogData, {
|
||||
str2Percent: true,
|
||||
})
|
||||
.saveAs('log_data.xlsx');
|
||||
}, [flattenLogData]);
|
||||
|
||||
const downloadCsvFile = useCallback((): void => {
|
||||
const csv = unparse(flattenLogData);
|
||||
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const csvUrl = URL.createObjectURL(csvBlob);
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = csvUrl;
|
||||
downloadLink.download = 'log_data.csv';
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
}, [flattenLogData]);
|
||||
|
||||
const menu: MenuProps = useMemo(
|
||||
() => ({
|
||||
items: [
|
||||
{
|
||||
key: 'download-as-excel',
|
||||
label: 'Excel',
|
||||
onClick: downloadExcelFile,
|
||||
},
|
||||
{
|
||||
key: 'download-as-csv',
|
||||
label: 'CSV',
|
||||
onClick: downloadCsvFile,
|
||||
},
|
||||
],
|
||||
}),
|
||||
[downloadCsvFile, downloadExcelFile],
|
||||
);
|
||||
|
||||
const isLoading = isLogsLoading || isLoadingAggregate;
|
||||
|
||||
if (liveTail !== 'STOPPED') {
|
||||
@ -157,12 +104,7 @@ function LogControls(): JSX.Element | null {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Dropdown menu={menu} trigger={['click']}>
|
||||
<DownloadLogButton loading={isLoading} size="small" type="link">
|
||||
<CloudDownloadOutlined />
|
||||
Download
|
||||
</DownloadLogButton>
|
||||
</Dropdown>
|
||||
<Download data={flattenLogData} isLoading={isLoading} fileName="log_data" />
|
||||
<Button
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
|
@ -92,8 +92,8 @@ function Application(): JSX.Element {
|
||||
|
||||
const {
|
||||
data: topLevelOperations,
|
||||
isLoading: topLevelOperationsLoading,
|
||||
error: topLevelOperationsError,
|
||||
isLoading: topLevelOperationsIsLoading,
|
||||
isError: topLevelOperationsIsError,
|
||||
} = useQuery<ServiceDataProps>({
|
||||
queryKey: [servicename, minTime, maxTime, selectedTags],
|
||||
@ -199,7 +199,7 @@ function Application(): JSX.Element {
|
||||
selectedTimeStamp={selectedTimeStamp}
|
||||
selectedTraceTags={selectedTraceTags}
|
||||
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||
topLevelOperationsIsLoading={topLevelOperationsIsLoading}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@ -220,11 +220,11 @@ function Application(): JSX.Element {
|
||||
handleGraphClick={handleGraphClick}
|
||||
onDragSelect={onDragSelect}
|
||||
topLevelOperationsError={topLevelOperationsError}
|
||||
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||
topLevelOperationsIsError={topLevelOperationsIsError}
|
||||
name="operations_per_sec"
|
||||
widget={operationPerSecWidget}
|
||||
opName="Rate"
|
||||
topLevelOperationsIsLoading={topLevelOperationsIsLoading}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -264,11 +264,11 @@ function Application(): JSX.Element {
|
||||
handleGraphClick={handleGraphClick}
|
||||
onDragSelect={onDragSelect}
|
||||
topLevelOperationsError={topLevelOperationsError}
|
||||
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||
topLevelOperationsIsError={topLevelOperationsIsError}
|
||||
name="error_percentage_%"
|
||||
widget={errorPercentageWidget}
|
||||
opName="Error"
|
||||
topLevelOperationsIsLoading={topLevelOperationsIsLoading}
|
||||
/>
|
||||
</ColErrorContainer>
|
||||
</Col>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import Spinner from 'components/Spinner';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import Graph from 'container/GridCardLayout/GridCard';
|
||||
@ -25,7 +24,7 @@ function ServiceOverview({
|
||||
selectedTraceTags,
|
||||
selectedTimeStamp,
|
||||
topLevelOperationsRoute,
|
||||
topLevelOperationsLoading,
|
||||
topLevelOperationsIsLoading,
|
||||
}: ServiceOverviewProps): JSX.Element {
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
|
||||
@ -64,15 +63,8 @@ function ServiceOverview({
|
||||
[servicename, isSpanMetricEnable, topLevelOperationsRoute, tagFilterItems],
|
||||
);
|
||||
|
||||
const isQueryEnabled = topLevelOperationsRoute.length > 0;
|
||||
|
||||
if (topLevelOperationsLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<Spinner height="40vh" tip="Loading..." />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
const isQueryEnabled =
|
||||
!topLevelOperationsIsLoading && topLevelOperationsRoute.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -109,7 +101,7 @@ interface ServiceOverviewProps {
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
handleGraphClick: (type: string) => ClickHandlerType;
|
||||
topLevelOperationsRoute: string[];
|
||||
topLevelOperationsLoading: boolean;
|
||||
topLevelOperationsIsLoading: boolean;
|
||||
}
|
||||
|
||||
export default ServiceOverview;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Typography } from 'antd';
|
||||
import axios from 'axios';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import Graph from 'container/GridCardLayout/GridCard';
|
||||
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
|
||||
@ -13,10 +12,10 @@ function TopLevelOperation({
|
||||
opName,
|
||||
topLevelOperationsIsError,
|
||||
topLevelOperationsError,
|
||||
topLevelOperationsLoading,
|
||||
onDragSelect,
|
||||
handleGraphClick,
|
||||
widget,
|
||||
topLevelOperationsIsLoading,
|
||||
}: TopLevelOperationProps): JSX.Element {
|
||||
return (
|
||||
<Card>
|
||||
@ -28,17 +27,13 @@ function TopLevelOperation({
|
||||
</Typography>
|
||||
) : (
|
||||
<GraphContainer>
|
||||
{topLevelOperationsLoading && (
|
||||
<Spinner size="large" tip="Loading..." height="40vh" />
|
||||
)}
|
||||
{!topLevelOperationsLoading && (
|
||||
<Graph
|
||||
name={name}
|
||||
widget={widget}
|
||||
onClickHandler={handleGraphClick(opName)}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
)}
|
||||
<Graph
|
||||
name={name}
|
||||
widget={widget}
|
||||
onClickHandler={handleGraphClick(opName)}
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={!topLevelOperationsIsLoading}
|
||||
/>
|
||||
</GraphContainer>
|
||||
)}
|
||||
</Card>
|
||||
@ -50,10 +45,10 @@ interface TopLevelOperationProps {
|
||||
opName: string;
|
||||
topLevelOperationsIsError: boolean;
|
||||
topLevelOperationsError: unknown;
|
||||
topLevelOperationsLoading: boolean;
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
handleGraphClick: (type: string) => ClickHandlerType;
|
||||
widget: Widgets;
|
||||
topLevelOperationsIsLoading: boolean;
|
||||
}
|
||||
|
||||
export default TopLevelOperation;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import getTopOperations from 'api/metrics/getTopOperations';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
@ -36,12 +35,7 @@ function TopOperation(): JSX.Element {
|
||||
|
||||
const topOperationData = data || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && <Spinner size="large" tip="Loading..." height="40vh" />}
|
||||
{!isLoading && <TopOperationsTable data={topOperationData} />}
|
||||
</>
|
||||
);
|
||||
return <TopOperationsTable data={topOperationData} isLoading={isLoading} />;
|
||||
}
|
||||
|
||||
export default TopOperation;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { topOperationMetricsDownloadOptions } from 'container/MetricsApplication/constant';
|
||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||
import { topOperationQueries } from 'container/MetricsApplication/MetricsPageQueries/TopOperationQueries';
|
||||
import { QueryTable } from 'container/QueryTable';
|
||||
@ -109,6 +110,7 @@ function TopOperationMetrics(): JSX.Element {
|
||||
queryTableData={queryTableData}
|
||||
loading={isLoading}
|
||||
renderColumnCell={renderColumnCell}
|
||||
downloadOption={topOperationMetricsDownloadOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
.top-operation {
|
||||
position: relative;
|
||||
.top-operation--download {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
@ -1,16 +1,32 @@
|
||||
import { Tooltip, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import './TopOperationsTable.styles.scss';
|
||||
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { InputRef, Tooltip, Typography } from 'antd';
|
||||
import { ColumnsType, ColumnType } from 'antd/lib/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import Download from 'container/Download/Download';
|
||||
import { filterDropdown } from 'container/ServiceApplication/Filter/FilterDropdown';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import { useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getErrorRate, navigateToTrace } from './utils';
|
||||
import { IServiceName } from './Tabs/types';
|
||||
import {
|
||||
convertedTracesToDownloadData,
|
||||
getErrorRate,
|
||||
navigateToTrace,
|
||||
} from './utils';
|
||||
|
||||
function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element {
|
||||
function TopOperationsTable({
|
||||
data,
|
||||
isLoading,
|
||||
}: TopOperationsTableProps): JSX.Element {
|
||||
const searchInput = useRef<InputRef>(null);
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
@ -34,19 +50,35 @@ function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element {
|
||||
});
|
||||
};
|
||||
|
||||
const getSearchOption = (): ColumnType<TopOperationList> => ({
|
||||
filterDropdown,
|
||||
filterIcon: <SearchOutlined />,
|
||||
onFilter: (value, record): boolean =>
|
||||
record.name
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes((value as string).toLowerCase()),
|
||||
onFilterDropdownOpenChange: (visible): void => {
|
||||
if (visible) {
|
||||
setTimeout(() => searchInput.current?.select(), 100);
|
||||
}
|
||||
},
|
||||
render: (text: string): JSX.Element => (
|
||||
<Tooltip placement="topLeft" title={text}>
|
||||
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
||||
{text}
|
||||
</Typography.Link>
|
||||
</Tooltip>
|
||||
),
|
||||
});
|
||||
|
||||
const columns: ColumnsType<TopOperationList> = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
render: (text: string): JSX.Element => (
|
||||
<Tooltip placement="topLeft" title={text}>
|
||||
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
||||
{text}
|
||||
</Typography.Link>
|
||||
</Tooltip>
|
||||
),
|
||||
...getSearchOption(),
|
||||
},
|
||||
{
|
||||
title: 'P50 (in ms)',
|
||||
@ -92,15 +124,27 @@ function TopOperationsTable({ data }: TopOperationsTableProps): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const downloadableData = convertedTracesToDownloadData(data);
|
||||
|
||||
return (
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
showHeader
|
||||
title={(): string => 'Key Operations'}
|
||||
tableLayout="fixed"
|
||||
dataSource={data}
|
||||
rowKey="name"
|
||||
/>
|
||||
<div className="top-operation">
|
||||
<div className="top-operation--download">
|
||||
<Download
|
||||
data={downloadableData}
|
||||
isLoading={isLoading}
|
||||
fileName={`top-operations-${servicename}`}
|
||||
/>
|
||||
</div>
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
loading={isLoading}
|
||||
showHeader
|
||||
title={(): string => 'Key Operations'}
|
||||
tableLayout="fixed"
|
||||
dataSource={data}
|
||||
rowKey="name"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -115,6 +159,7 @@ export interface TopOperationList {
|
||||
|
||||
interface TopOperationsTableProps {
|
||||
data: TopOperationList[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export default TopOperationsTable;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { DownloadOptions } from 'container/Download/Download.types';
|
||||
|
||||
export const legend = {
|
||||
address: '{{address}}',
|
||||
};
|
||||
@ -67,3 +69,8 @@ export enum WidgetKeys {
|
||||
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
|
||||
Signoz_latency_bucket = 'signoz_latency_bucket',
|
||||
}
|
||||
|
||||
export const topOperationMetricsDownloadOptions: DownloadOptions = {
|
||||
isDownloadEnabled: true,
|
||||
fileName: 'top-operation',
|
||||
} as const;
|
||||
|
@ -35,3 +35,19 @@ export const getNearestHighestBucketValue = (
|
||||
|
||||
export const convertMilSecToNanoSec = (value: number): number =>
|
||||
value * 1000000000;
|
||||
|
||||
export const convertedTracesToDownloadData = (
|
||||
originalData: TopOperationList[],
|
||||
): Record<string, string>[] =>
|
||||
originalData.map((item) => {
|
||||
const newObj: Record<string, string> = {
|
||||
Name: item.name,
|
||||
'P50 (in ms)': (item.p50 / 1000000).toFixed(2),
|
||||
'P95 (in ms)': (item.p95 / 1000000).toFixed(2),
|
||||
'P99 (in ms)': (item.p99 / 1000000).toFixed(2),
|
||||
'Number of calls': item.numCalls.toString(),
|
||||
'Error Rate (%)': getErrorRate(item).toFixed(2),
|
||||
};
|
||||
|
||||
return newObj;
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
CloseCircleFilled,
|
||||
ExclamationCircleFilled,
|
||||
LoadingOutlined,
|
||||
MinusCircleFilled,
|
||||
} from '@ant-design/icons';
|
||||
import { Spin } from 'antd';
|
||||
|
||||
@ -16,6 +17,8 @@ export function getDeploymentStage(value: string): string {
|
||||
return 'Dirty';
|
||||
case 'FAILED':
|
||||
return 'Failed';
|
||||
case 'UNKNOWN':
|
||||
return 'Unknown';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@ -33,6 +36,8 @@ export function getDeploymentStageIcon(value: string): JSX.Element {
|
||||
return <ExclamationCircleFilled />;
|
||||
case 'FAILED':
|
||||
return <CloseCircleFilled />;
|
||||
case 'UNKNOWN':
|
||||
return <MinusCircleFilled />;
|
||||
default:
|
||||
return <span />;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Input } from 'antd';
|
||||
import React, { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { BaseSyntheticEvent, Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function PipelinesSearchSection({
|
||||
@ -7,18 +8,18 @@ function PipelinesSearchSection({
|
||||
}: PipelinesSearchSectionProps): JSX.Element {
|
||||
const { t } = useTranslation(['pipeline']);
|
||||
|
||||
const onSeachHandler = useCallback(
|
||||
(event: React.SetStateAction<string>) => {
|
||||
setPipelineSearchValue(event);
|
||||
},
|
||||
[setPipelineSearchValue],
|
||||
);
|
||||
const handleSearch = (searchEv: BaseSyntheticEvent): void => {
|
||||
setPipelineSearchValue(searchEv?.target?.value || '');
|
||||
};
|
||||
|
||||
const debouncedHandleSearch = debounce(handleSearch, 300);
|
||||
|
||||
return (
|
||||
<Input.Search
|
||||
<Input
|
||||
type="text"
|
||||
allowClear
|
||||
placeholder={t('search_pipeline_placeholder')}
|
||||
onSearch={onSeachHandler}
|
||||
onChange={debouncedHandleSearch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -16,11 +16,7 @@ function DescriptionTextArea({
|
||||
label={<FormLabelStyle>{fieldData.fieldName}</FormLabelStyle>}
|
||||
key={fieldData.id}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
name={fieldData.name}
|
||||
placeholder={t(fieldData.placeholder)}
|
||||
/>
|
||||
<Input.TextArea rows={3} placeholder={t(fieldData.placeholder)} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ function NameInput({ fieldData }: NameInputProps): JSX.Element {
|
||||
rules={formValidationRules}
|
||||
name={fieldData.name}
|
||||
>
|
||||
<Input name={fieldData.name} placeholder={t(fieldData.placeholder)} />
|
||||
<Input placeholder={t(fieldData.placeholder)} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
@ -22,8 +22,9 @@ function NameInput({ fieldData }: NameInputProps): JSX.Element {
|
||||
name={fieldData.name}
|
||||
initialValue={fieldData.initialValue}
|
||||
rules={fieldData.rules ? fieldData.rules : formValidationRules}
|
||||
dependencies={fieldData.dependencies || []}
|
||||
>
|
||||
<Input placeholder={t(fieldData.placeholder)} name={fieldData.name} />
|
||||
<Input placeholder={t(fieldData.placeholder)} />
|
||||
</Form.Item>
|
||||
</FormWrapper>
|
||||
</Container>
|
||||
|
@ -20,11 +20,7 @@ function ParsingRulesTextArea({
|
||||
name={fieldData.name}
|
||||
label={<ModalFooterTitle>{fieldData.fieldName}</ModalFooterTitle>}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
name={fieldData.name}
|
||||
placeholder={t(fieldData.placeholder)}
|
||||
/>
|
||||
<Input.TextArea rows={4} placeholder={t(fieldData.placeholder)} />
|
||||
</Form.Item>
|
||||
</FormWrapper>
|
||||
</Container>
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { Rule, RuleRender } from 'antd/es/form';
|
||||
import { NamePath } from 'antd/es/form/interface';
|
||||
|
||||
type ProcessorType = {
|
||||
key: string;
|
||||
value: string;
|
||||
@ -8,11 +11,11 @@ type ProcessorType = {
|
||||
|
||||
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: 'json_parser', value: 'json_parser', label: 'Json Parser' },
|
||||
{ key: 'trace_parser', value: 'trace_parser', label: 'Trace Parser' },
|
||||
{ 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' },
|
||||
@ -24,11 +27,30 @@ export type ProcessorFormField = {
|
||||
id: number;
|
||||
fieldName: string;
|
||||
placeholder: string;
|
||||
name: string;
|
||||
rules?: Array<{ [key: string]: boolean }>;
|
||||
name: string | NamePath;
|
||||
rules?: Array<Rule>;
|
||||
initialValue?: string;
|
||||
dependencies?: Array<string | NamePath>;
|
||||
};
|
||||
|
||||
const traceParserFieldValidator: RuleRender = (form) => ({
|
||||
validator: (): Promise<void> => {
|
||||
const parseFromValues = [
|
||||
['trace_id', 'parse_from'],
|
||||
['span_id', 'parse_from'],
|
||||
['trace_flags', 'parse_from'],
|
||||
].map((np) => form.getFieldValue(np));
|
||||
|
||||
if (!parseFromValues.some((v) => v?.length > 0)) {
|
||||
return Promise.reject(
|
||||
new Error('At least one of the trace parser fields must be specified.'),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
const commonFields = [
|
||||
{
|
||||
id: 3,
|
||||
@ -152,21 +174,36 @@ export const processorFields: { [key: string]: Array<ProcessorFormField> } = {
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fieldName: 'Trace Id Parce From',
|
||||
fieldName: 'Parse Trace Id From',
|
||||
placeholder: 'processor_trace_id_placeholder',
|
||||
name: 'traceId',
|
||||
name: ['trace_id', 'parse_from'],
|
||||
rules: [traceParserFieldValidator],
|
||||
dependencies: [
|
||||
['span_id', 'parse_from'],
|
||||
['trace_flags', 'parse_from'],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fieldName: 'Span id Parse From',
|
||||
fieldName: 'Parse Span Id From',
|
||||
placeholder: 'processor_span_id_placeholder',
|
||||
name: 'spanId',
|
||||
name: ['span_id', 'parse_from'],
|
||||
rules: [traceParserFieldValidator],
|
||||
dependencies: [
|
||||
['trace_id', 'parse_from'],
|
||||
['trace_flags', 'parse_from'],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
fieldName: 'Trace flags parse from',
|
||||
fieldName: 'Parse Trace flags From',
|
||||
placeholder: 'processor_trace_flags_placeholder',
|
||||
name: 'traceFlags',
|
||||
name: ['trace_flags', 'parse_from'],
|
||||
rules: [traceParserFieldValidator],
|
||||
dependencies: [
|
||||
['trace_id', 'parse_from'],
|
||||
['span_id', 'parse_from'],
|
||||
],
|
||||
},
|
||||
],
|
||||
retain: [
|
||||
|
@ -4,6 +4,6 @@ import NameInput from './FormFields/NameInput';
|
||||
export const renderProcessorForm = (
|
||||
processorType: string,
|
||||
): Array<JSX.Element> =>
|
||||
processorFields[processorType]?.map((fieldName: ProcessorFormField) => (
|
||||
<NameInput key={fieldName.id} fieldData={fieldName} />
|
||||
processorFields[processorType]?.map((fieldData: ProcessorFormField) => (
|
||||
<NameInput key={fieldData.id} fieldData={fieldData} />
|
||||
));
|
||||
|
@ -14,8 +14,8 @@ import {
|
||||
|
||||
import { tableComponents } from '../config';
|
||||
import { ModalFooterTitle } from '../styles';
|
||||
import { AlertMessage } from '.';
|
||||
import { processorColumns } from './config';
|
||||
import { AlertMessage } from './PipelineListsView';
|
||||
import { FooterButton, StyledTable } from './styles';
|
||||
import DragAction from './TableComponents/DragAction';
|
||||
import ProcessorActions from './TableComponents/ProcessorActions';
|
||||
|
@ -0,0 +1,489 @@
|
||||
import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { Modal, Table } from 'antd';
|
||||
import { ExpandableConfig } from 'antd/es/table/interface';
|
||||
import savePipeline from 'api/pipeline/post';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActionMode,
|
||||
ActionType,
|
||||
Pipeline,
|
||||
PipelineData,
|
||||
ProcessorData,
|
||||
} from 'types/api/pipeline/def';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { tableComponents } from '../config';
|
||||
import AddNewPipeline from './AddNewPipeline';
|
||||
import AddNewProcessor from './AddNewProcessor';
|
||||
import { pipelineColumns } from './config';
|
||||
import ModeAndConfiguration from './ModeAndConfiguration';
|
||||
import PipelineExpanView from './PipelineExpandView';
|
||||
import SaveConfigButton from './SaveConfigButton';
|
||||
import {
|
||||
AlertContentWrapper,
|
||||
AlertModalTitle,
|
||||
Container,
|
||||
FooterButton,
|
||||
} from './styles';
|
||||
import DragAction from './TableComponents/DragAction';
|
||||
import PipelineActions from './TableComponents/PipelineActions';
|
||||
import PreviewAction from './TableComponents/PipelineActions/components/PreviewAction';
|
||||
import TableExpandIcon from './TableComponents/TableExpandIcon';
|
||||
import {
|
||||
getDataOnSearch,
|
||||
getEditedDataSource,
|
||||
getElementFromArray,
|
||||
getRecordIndex,
|
||||
getTableColumn,
|
||||
getUpdatedRow,
|
||||
} from './utils';
|
||||
|
||||
function PipelineListsView({
|
||||
isActionType,
|
||||
setActionType,
|
||||
isActionMode,
|
||||
setActionMode,
|
||||
pipelineData,
|
||||
refetchPipelineLists,
|
||||
pipelineSearchValue,
|
||||
}: PipelineListsViewProps): JSX.Element {
|
||||
const { t } = useTranslation(['pipeline', 'common']);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const { notifications } = useNotifications();
|
||||
const [prevPipelineData, setPrevPipelineData] = useState<Array<PipelineData>>(
|
||||
cloneDeep(pipelineData?.pipelines || []),
|
||||
);
|
||||
const [currPipelineData, setCurrPipelineData] = useState<Array<PipelineData>>(
|
||||
cloneDeep(pipelineData?.pipelines || []),
|
||||
);
|
||||
|
||||
const [expandedPipelineId, setExpandedPipelineId] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const expandedPipelineData = useCallback(
|
||||
() => currPipelineData?.find((p) => p.id === expandedPipelineId),
|
||||
[currPipelineData, expandedPipelineId],
|
||||
);
|
||||
const setExpandedPipelineData = useCallback(
|
||||
(newData: PipelineData): void => {
|
||||
if (expandedPipelineId) {
|
||||
const pipelineIdx = currPipelineData?.findIndex(
|
||||
(p) => p.id === expandedPipelineId,
|
||||
);
|
||||
if (pipelineIdx >= 0) {
|
||||
const newPipelineData = [...currPipelineData];
|
||||
newPipelineData[pipelineIdx] = newData;
|
||||
setCurrPipelineData(newPipelineData);
|
||||
}
|
||||
}
|
||||
},
|
||||
[expandedPipelineId, currPipelineData],
|
||||
);
|
||||
|
||||
const [
|
||||
selectedProcessorData,
|
||||
setSelectedProcessorData,
|
||||
] = useState<ProcessorData>();
|
||||
|
||||
const [
|
||||
selectedPipelineData,
|
||||
setSelectedPipelineData,
|
||||
] = useState<PipelineData>();
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<Array<string>>();
|
||||
const [showSaveButton, setShowSaveButton] = useState<string>();
|
||||
const isEditingActionMode = isActionMode === ActionMode.Editing;
|
||||
|
||||
const visibleCurrPipelines = useMemo((): Array<PipelineData> => {
|
||||
if (pipelineSearchValue === '') {
|
||||
return currPipelineData;
|
||||
}
|
||||
return currPipelineData.filter((data) =>
|
||||
getDataOnSearch(data as never, pipelineSearchValue),
|
||||
);
|
||||
}, [currPipelineData, pipelineSearchValue]);
|
||||
|
||||
const handleAlert = useCallback(
|
||||
({ title, descrition, buttontext, onCancel, onOk }: AlertMessage) => {
|
||||
modal.confirm({
|
||||
title: <AlertModalTitle>{title}</AlertModalTitle>,
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: <AlertContentWrapper>{descrition}</AlertContentWrapper>,
|
||||
okText: <span>{buttontext}</span>,
|
||||
cancelText: <span>{t('cancel')}</span>,
|
||||
onOk,
|
||||
onCancel,
|
||||
});
|
||||
},
|
||||
[modal, t],
|
||||
);
|
||||
|
||||
const pipelineEditAction = useCallback(
|
||||
(record: PipelineData) => (): void => {
|
||||
setActionType(ActionType.EditPipeline);
|
||||
setSelectedPipelineData(record);
|
||||
},
|
||||
[setActionType],
|
||||
);
|
||||
|
||||
const pipelineDeleteHandler = useCallback(
|
||||
(record: PipelineData) => (): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
const filteredData = getElementFromArray(currPipelineData, record, 'id');
|
||||
filteredData.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
});
|
||||
setCurrPipelineData(filteredData);
|
||||
},
|
||||
[currPipelineData],
|
||||
);
|
||||
|
||||
const pipelineDeleteAction = useCallback(
|
||||
(record: PipelineData) => (): void => {
|
||||
handleAlert({
|
||||
title: `${t('delete_pipeline')} : ${record.name}?`,
|
||||
descrition: t('delete_pipeline_description'),
|
||||
buttontext: t('delete'),
|
||||
onOk: pipelineDeleteHandler(record),
|
||||
});
|
||||
},
|
||||
[handleAlert, pipelineDeleteHandler, t],
|
||||
);
|
||||
|
||||
const processorEditAction = useCallback(
|
||||
(record: ProcessorData) => (): void => {
|
||||
setActionType(ActionType.EditProcessor);
|
||||
setSelectedProcessorData(record);
|
||||
},
|
||||
[setActionType],
|
||||
);
|
||||
|
||||
const onSwitchPipelineChange = useCallback(
|
||||
(checked: boolean, record: PipelineData): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
const findRecordIndex = getRecordIndex(currPipelineData, record, 'id');
|
||||
const updateSwitch = {
|
||||
...currPipelineData[findRecordIndex],
|
||||
enabled: checked,
|
||||
};
|
||||
const editedPipelineData = getEditedDataSource(
|
||||
currPipelineData,
|
||||
record,
|
||||
'id',
|
||||
updateSwitch,
|
||||
);
|
||||
setCurrPipelineData(editedPipelineData);
|
||||
},
|
||||
[currPipelineData],
|
||||
);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const fieldColumns = getTableColumn(pipelineColumns);
|
||||
if (isEditingActionMode) {
|
||||
fieldColumns.push(
|
||||
{
|
||||
title: 'Actions',
|
||||
dataIndex: 'smartAction',
|
||||
key: 'smartAction',
|
||||
align: 'center',
|
||||
render: (_value, record): JSX.Element => (
|
||||
<PipelineActions
|
||||
pipeline={record}
|
||||
editAction={pipelineEditAction(record)}
|
||||
deleteAction={pipelineDeleteAction(record)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'enabled',
|
||||
key: 'enabled',
|
||||
render: (value, record) => (
|
||||
<DragAction
|
||||
isEnabled={value}
|
||||
onChange={(checked: boolean): void =>
|
||||
onSwitchPipelineChange(checked, record)
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
fieldColumns.push({
|
||||
title: 'Actions',
|
||||
dataIndex: 'smartAction',
|
||||
key: 'smartAction',
|
||||
align: 'center',
|
||||
render: (_value, record): JSX.Element => (
|
||||
<PreviewAction pipeline={record} />
|
||||
),
|
||||
});
|
||||
}
|
||||
return fieldColumns;
|
||||
}, [
|
||||
isEditingActionMode,
|
||||
pipelineEditAction,
|
||||
pipelineDeleteAction,
|
||||
onSwitchPipelineChange,
|
||||
]);
|
||||
|
||||
const updatePipelineSequence = useCallback(
|
||||
(updatedRow: PipelineData[]) => (): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
setCurrPipelineData(updatedRow);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onCancelPipelineSequence = useCallback(
|
||||
(rawData: PipelineData[]) => (): void => {
|
||||
setCurrPipelineData(rawData);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const movePipelineRow = useCallback(
|
||||
(dragIndex: number, hoverIndex: number) => {
|
||||
if (currPipelineData && isEditingActionMode) {
|
||||
const rawData = currPipelineData;
|
||||
|
||||
const updatedRows = getUpdatedRow(
|
||||
currPipelineData,
|
||||
visibleCurrPipelines[dragIndex].orderId - 1,
|
||||
visibleCurrPipelines[hoverIndex].orderId - 1,
|
||||
);
|
||||
|
||||
updatedRows.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
});
|
||||
handleAlert({
|
||||
title: t('reorder_pipeline'),
|
||||
descrition: t('reorder_pipeline_description'),
|
||||
buttontext: t('reorder'),
|
||||
onOk: updatePipelineSequence(updatedRows),
|
||||
onCancel: onCancelPipelineSequence(rawData),
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
currPipelineData,
|
||||
isEditingActionMode,
|
||||
visibleCurrPipelines,
|
||||
handleAlert,
|
||||
t,
|
||||
updatePipelineSequence,
|
||||
onCancelPipelineSequence,
|
||||
],
|
||||
);
|
||||
|
||||
const expandedRowView = useCallback(
|
||||
(): JSX.Element => (
|
||||
<PipelineExpanView
|
||||
handleAlert={handleAlert}
|
||||
isActionMode={isActionMode}
|
||||
setActionType={setActionType}
|
||||
processorEditAction={processorEditAction}
|
||||
setShowSaveButton={setShowSaveButton}
|
||||
expandedPipelineData={expandedPipelineData()}
|
||||
setExpandedPipelineData={setExpandedPipelineData}
|
||||
prevPipelineData={prevPipelineData}
|
||||
/>
|
||||
),
|
||||
[
|
||||
handleAlert,
|
||||
processorEditAction,
|
||||
isActionMode,
|
||||
expandedPipelineData,
|
||||
setActionType,
|
||||
prevPipelineData,
|
||||
setExpandedPipelineData,
|
||||
],
|
||||
);
|
||||
|
||||
const onExpand = useCallback(
|
||||
(expanded: boolean, record: PipelineData): void => {
|
||||
const keys = [];
|
||||
if (expanded && record.id) {
|
||||
keys.push(record?.id);
|
||||
}
|
||||
setExpandedRowKeys(keys);
|
||||
setExpandedPipelineId(record.id);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const getExpandIcon = (
|
||||
expanded: boolean,
|
||||
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void,
|
||||
record: PipelineData,
|
||||
): JSX.Element => (
|
||||
<TableExpandIcon expanded={expanded} onExpand={onExpand} record={record} />
|
||||
);
|
||||
|
||||
const addNewPipelineHandler = useCallback((): void => {
|
||||
setActionType(ActionType.AddPipeline);
|
||||
}, [setActionType]);
|
||||
|
||||
const footer = useCallback((): JSX.Element | undefined => {
|
||||
if (isEditingActionMode) {
|
||||
return (
|
||||
<FooterButton
|
||||
type="link"
|
||||
onClick={addNewPipelineHandler}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('add_new_pipeline')}
|
||||
</FooterButton>
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}, [isEditingActionMode, addNewPipelineHandler, t]);
|
||||
|
||||
const onSaveConfigurationHandler = useCallback(async () => {
|
||||
const modifiedPipelineData = currPipelineData.map((item: PipelineData) => {
|
||||
const pipelineData = { ...item };
|
||||
delete pipelineData?.id;
|
||||
return pipelineData;
|
||||
});
|
||||
const response = await savePipeline({
|
||||
data: { pipelines: modifiedPipelineData },
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
refetchPipelineLists();
|
||||
setActionMode(ActionMode.Viewing);
|
||||
setShowSaveButton(undefined);
|
||||
setCurrPipelineData(response.payload?.pipelines || []);
|
||||
setPrevPipelineData(response.payload?.pipelines || []);
|
||||
} else {
|
||||
modifiedPipelineData.forEach((item: PipelineData) => {
|
||||
const pipelineData = item;
|
||||
pipelineData.id = v4();
|
||||
return pipelineData;
|
||||
});
|
||||
setActionMode(ActionMode.Editing);
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('something_went_wrong'),
|
||||
});
|
||||
setCurrPipelineData(modifiedPipelineData);
|
||||
setPrevPipelineData(modifiedPipelineData);
|
||||
}
|
||||
}, [currPipelineData, notifications, refetchPipelineLists, setActionMode, t]);
|
||||
|
||||
const onCancelConfigurationHandler = useCallback((): void => {
|
||||
setActionMode(ActionMode.Viewing);
|
||||
setShowSaveButton(undefined);
|
||||
prevPipelineData.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
if (obj.config) {
|
||||
obj.config?.forEach((configItem, index) => {
|
||||
const config = configItem;
|
||||
config.orderId = index + 1;
|
||||
});
|
||||
for (let i = 0; i < obj.config.length - 1; i += 1) {
|
||||
obj.config[i].output = obj.config[i + 1].id;
|
||||
}
|
||||
}
|
||||
});
|
||||
setCurrPipelineData(prevPipelineData);
|
||||
setExpandedRowKeys([]);
|
||||
}, [prevPipelineData, setActionMode]);
|
||||
|
||||
const onRowHandler = (
|
||||
_data: PipelineData,
|
||||
index?: number,
|
||||
): React.HTMLAttributes<unknown> =>
|
||||
({
|
||||
index,
|
||||
moveRow: movePipelineRow,
|
||||
} as React.HTMLAttributes<unknown>);
|
||||
|
||||
const expandableConfig: ExpandableConfig<PipelineData> = {
|
||||
expandedRowKeys,
|
||||
onExpand,
|
||||
expandIcon: ({ expanded, onExpand, record }: ExpandRowConfig) =>
|
||||
getExpandIcon(expanded, onExpand, record),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
<AddNewPipeline
|
||||
isActionType={isActionType}
|
||||
setActionType={setActionType}
|
||||
selectedPipelineData={selectedPipelineData}
|
||||
setShowSaveButton={setShowSaveButton}
|
||||
setCurrPipelineData={setCurrPipelineData}
|
||||
currPipelineData={currPipelineData}
|
||||
/>
|
||||
<AddNewProcessor
|
||||
isActionType={isActionType}
|
||||
setActionType={setActionType}
|
||||
selectedProcessorData={selectedProcessorData}
|
||||
setShowSaveButton={setShowSaveButton}
|
||||
expandedPipelineData={expandedPipelineData()}
|
||||
setExpandedPipelineData={setExpandedPipelineData}
|
||||
/>
|
||||
<Container>
|
||||
<ModeAndConfiguration
|
||||
isActionMode={isActionMode}
|
||||
version={pipelineData?.version}
|
||||
/>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
expandedRowRender={expandedRowView}
|
||||
expandable={expandableConfig}
|
||||
components={tableComponents}
|
||||
dataSource={visibleCurrPipelines}
|
||||
onRow={onRowHandler}
|
||||
footer={footer}
|
||||
pagination={false}
|
||||
/>
|
||||
</DndProvider>
|
||||
{showSaveButton && (
|
||||
<SaveConfigButton
|
||||
onSaveConfigurationHandler={onSaveConfigurationHandler}
|
||||
onCancelConfigurationHandler={onCancelConfigurationHandler}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface PipelineListsViewProps {
|
||||
isActionType: string;
|
||||
setActionType: (actionType?: ActionType) => void;
|
||||
isActionMode: string;
|
||||
setActionMode: (actionMode: ActionMode) => void;
|
||||
pipelineData: Pipeline;
|
||||
refetchPipelineLists: VoidFunction;
|
||||
pipelineSearchValue: string;
|
||||
}
|
||||
|
||||
interface ExpandRowConfig {
|
||||
expanded: boolean;
|
||||
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void;
|
||||
record: PipelineData;
|
||||
}
|
||||
|
||||
export interface AlertMessage {
|
||||
title: string;
|
||||
descrition: string;
|
||||
buttontext: string;
|
||||
onOk: VoidFunction;
|
||||
onCancel?: VoidFunction;
|
||||
}
|
||||
|
||||
export default PipelineListsView;
|
@ -3,7 +3,6 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
|
||||
box-sizing: border-box;
|
||||
@ -11,8 +10,9 @@
|
||||
}
|
||||
|
||||
.logs-preview-list-item {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-height: 2rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -1,489 +1,3 @@
|
||||
import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { Modal, Table } from 'antd';
|
||||
import { ExpandableConfig } from 'antd/es/table/interface';
|
||||
import savePipeline from 'api/pipeline/post';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActionMode,
|
||||
ActionType,
|
||||
Pipeline,
|
||||
PipelineData,
|
||||
ProcessorData,
|
||||
} from 'types/api/pipeline/def';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { tableComponents } from '../config';
|
||||
import AddNewPipeline from './AddNewPipeline';
|
||||
import AddNewProcessor from './AddNewProcessor';
|
||||
import { pipelineColumns } from './config';
|
||||
import ModeAndConfiguration from './ModeAndConfiguration';
|
||||
import PipelineExpanView from './PipelineExpandView';
|
||||
import SaveConfigButton from './SaveConfigButton';
|
||||
import {
|
||||
AlertContentWrapper,
|
||||
AlertModalTitle,
|
||||
Container,
|
||||
FooterButton,
|
||||
} from './styles';
|
||||
import DragAction from './TableComponents/DragAction';
|
||||
import PipelineActions from './TableComponents/PipelineActions';
|
||||
import PreviewAction from './TableComponents/PipelineActions/components/PreviewAction';
|
||||
import TableExpandIcon from './TableComponents/TableExpandIcon';
|
||||
import {
|
||||
getDataOnSearch,
|
||||
getEditedDataSource,
|
||||
getElementFromArray,
|
||||
getRecordIndex,
|
||||
getTableColumn,
|
||||
getUpdatedRow,
|
||||
} from './utils';
|
||||
|
||||
function PipelineListsView({
|
||||
isActionType,
|
||||
setActionType,
|
||||
isActionMode,
|
||||
setActionMode,
|
||||
pipelineData,
|
||||
refetchPipelineLists,
|
||||
pipelineSearchValue,
|
||||
}: PipelineListsViewProps): JSX.Element {
|
||||
const { t } = useTranslation(['pipeline', 'common']);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const { notifications } = useNotifications();
|
||||
const [prevPipelineData, setPrevPipelineData] = useState<Array<PipelineData>>(
|
||||
cloneDeep(pipelineData?.pipelines),
|
||||
);
|
||||
const [currPipelineData, setCurrPipelineData] = useState<Array<PipelineData>>(
|
||||
cloneDeep(pipelineData?.pipelines),
|
||||
);
|
||||
|
||||
const [expandedPipelineId, setExpandedPipelineId] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const expandedPipelineData = useCallback(
|
||||
() => currPipelineData.find((p) => p.id === expandedPipelineId),
|
||||
[currPipelineData, expandedPipelineId],
|
||||
);
|
||||
const setExpandedPipelineData = useCallback(
|
||||
(newData: PipelineData): void => {
|
||||
if (expandedPipelineId) {
|
||||
const pipelineIdx = currPipelineData.findIndex(
|
||||
(p) => p.id === expandedPipelineId,
|
||||
);
|
||||
if (pipelineIdx >= 0) {
|
||||
const newPipelineData = [...currPipelineData];
|
||||
newPipelineData[pipelineIdx] = newData;
|
||||
setCurrPipelineData(newPipelineData);
|
||||
}
|
||||
}
|
||||
},
|
||||
[expandedPipelineId, currPipelineData],
|
||||
);
|
||||
|
||||
const [
|
||||
selectedProcessorData,
|
||||
setSelectedProcessorData,
|
||||
] = useState<ProcessorData>();
|
||||
|
||||
const [
|
||||
selectedPipelineData,
|
||||
setSelectedPipelineData,
|
||||
] = useState<PipelineData>();
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<Array<string>>();
|
||||
const [showSaveButton, setShowSaveButton] = useState<string>();
|
||||
const isEditingActionMode = isActionMode === ActionMode.Editing;
|
||||
|
||||
const visibleCurrPipelines = useMemo((): Array<PipelineData> => {
|
||||
if (pipelineSearchValue === '') {
|
||||
return currPipelineData;
|
||||
}
|
||||
return currPipelineData.filter((data) =>
|
||||
getDataOnSearch(data as never, pipelineSearchValue),
|
||||
);
|
||||
}, [currPipelineData, pipelineSearchValue]);
|
||||
|
||||
const handleAlert = useCallback(
|
||||
({ title, descrition, buttontext, onCancel, onOk }: AlertMessage) => {
|
||||
modal.confirm({
|
||||
title: <AlertModalTitle>{title}</AlertModalTitle>,
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: <AlertContentWrapper>{descrition}</AlertContentWrapper>,
|
||||
okText: <span>{buttontext}</span>,
|
||||
cancelText: <span>{t('cancel')}</span>,
|
||||
onOk,
|
||||
onCancel,
|
||||
});
|
||||
},
|
||||
[modal, t],
|
||||
);
|
||||
|
||||
const pipelineEditAction = useCallback(
|
||||
(record: PipelineData) => (): void => {
|
||||
setActionType(ActionType.EditPipeline);
|
||||
setSelectedPipelineData(record);
|
||||
},
|
||||
[setActionType],
|
||||
);
|
||||
|
||||
const pipelineDeleteHandler = useCallback(
|
||||
(record: PipelineData) => (): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
const filteredData = getElementFromArray(currPipelineData, record, 'id');
|
||||
filteredData.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
});
|
||||
setCurrPipelineData(filteredData);
|
||||
},
|
||||
[currPipelineData],
|
||||
);
|
||||
|
||||
const pipelineDeleteAction = useCallback(
|
||||
(record: PipelineData) => (): void => {
|
||||
handleAlert({
|
||||
title: `${t('delete_pipeline')} : ${record.name}?`,
|
||||
descrition: t('delete_pipeline_description'),
|
||||
buttontext: t('delete'),
|
||||
onOk: pipelineDeleteHandler(record),
|
||||
});
|
||||
},
|
||||
[handleAlert, pipelineDeleteHandler, t],
|
||||
);
|
||||
|
||||
const processorEditAction = useCallback(
|
||||
(record: ProcessorData) => (): void => {
|
||||
setActionType(ActionType.EditProcessor);
|
||||
setSelectedProcessorData(record);
|
||||
},
|
||||
[setActionType],
|
||||
);
|
||||
|
||||
const onSwitchPipelineChange = useCallback(
|
||||
(checked: boolean, record: PipelineData): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
const findRecordIndex = getRecordIndex(currPipelineData, record, 'id');
|
||||
const updateSwitch = {
|
||||
...currPipelineData[findRecordIndex],
|
||||
enabled: checked,
|
||||
};
|
||||
const editedPipelineData = getEditedDataSource(
|
||||
currPipelineData,
|
||||
record,
|
||||
'id',
|
||||
updateSwitch,
|
||||
);
|
||||
setCurrPipelineData(editedPipelineData);
|
||||
},
|
||||
[currPipelineData],
|
||||
);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const fieldColumns = getTableColumn(pipelineColumns);
|
||||
if (isEditingActionMode) {
|
||||
fieldColumns.push(
|
||||
{
|
||||
title: 'Actions',
|
||||
dataIndex: 'smartAction',
|
||||
key: 'smartAction',
|
||||
align: 'center',
|
||||
render: (_value, record): JSX.Element => (
|
||||
<PipelineActions
|
||||
pipeline={record}
|
||||
editAction={pipelineEditAction(record)}
|
||||
deleteAction={pipelineDeleteAction(record)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'enabled',
|
||||
key: 'enabled',
|
||||
render: (value, record) => (
|
||||
<DragAction
|
||||
isEnabled={value}
|
||||
onChange={(checked: boolean): void =>
|
||||
onSwitchPipelineChange(checked, record)
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
fieldColumns.push({
|
||||
title: 'Actions',
|
||||
dataIndex: 'smartAction',
|
||||
key: 'smartAction',
|
||||
align: 'center',
|
||||
render: (_value, record): JSX.Element => (
|
||||
<PreviewAction pipeline={record} />
|
||||
),
|
||||
});
|
||||
}
|
||||
return fieldColumns;
|
||||
}, [
|
||||
isEditingActionMode,
|
||||
pipelineEditAction,
|
||||
pipelineDeleteAction,
|
||||
onSwitchPipelineChange,
|
||||
]);
|
||||
|
||||
const updatePipelineSequence = useCallback(
|
||||
(updatedRow: PipelineData[]) => (): void => {
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
setCurrPipelineData(updatedRow);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onCancelPipelineSequence = useCallback(
|
||||
(rawData: PipelineData[]) => (): void => {
|
||||
setCurrPipelineData(rawData);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const movePipelineRow = useCallback(
|
||||
(dragIndex: number, hoverIndex: number) => {
|
||||
if (currPipelineData && isEditingActionMode) {
|
||||
const rawData = currPipelineData;
|
||||
|
||||
const updatedRows = getUpdatedRow(
|
||||
currPipelineData,
|
||||
visibleCurrPipelines[dragIndex].orderId - 1,
|
||||
visibleCurrPipelines[hoverIndex].orderId - 1,
|
||||
);
|
||||
|
||||
updatedRows.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
});
|
||||
handleAlert({
|
||||
title: t('reorder_pipeline'),
|
||||
descrition: t('reorder_pipeline_description'),
|
||||
buttontext: t('reorder'),
|
||||
onOk: updatePipelineSequence(updatedRows),
|
||||
onCancel: onCancelPipelineSequence(rawData),
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
currPipelineData,
|
||||
isEditingActionMode,
|
||||
visibleCurrPipelines,
|
||||
handleAlert,
|
||||
t,
|
||||
updatePipelineSequence,
|
||||
onCancelPipelineSequence,
|
||||
],
|
||||
);
|
||||
|
||||
const expandedRowView = useCallback(
|
||||
(): JSX.Element => (
|
||||
<PipelineExpanView
|
||||
handleAlert={handleAlert}
|
||||
isActionMode={isActionMode}
|
||||
setActionType={setActionType}
|
||||
processorEditAction={processorEditAction}
|
||||
setShowSaveButton={setShowSaveButton}
|
||||
expandedPipelineData={expandedPipelineData()}
|
||||
setExpandedPipelineData={setExpandedPipelineData}
|
||||
prevPipelineData={prevPipelineData}
|
||||
/>
|
||||
),
|
||||
[
|
||||
handleAlert,
|
||||
processorEditAction,
|
||||
isActionMode,
|
||||
expandedPipelineData,
|
||||
setActionType,
|
||||
prevPipelineData,
|
||||
setExpandedPipelineData,
|
||||
],
|
||||
);
|
||||
|
||||
const onExpand = useCallback(
|
||||
(expanded: boolean, record: PipelineData): void => {
|
||||
const keys = [];
|
||||
if (expanded && record.id) {
|
||||
keys.push(record?.id);
|
||||
}
|
||||
setExpandedRowKeys(keys);
|
||||
setExpandedPipelineId(record.id);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const getExpandIcon = (
|
||||
expanded: boolean,
|
||||
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void,
|
||||
record: PipelineData,
|
||||
): JSX.Element => (
|
||||
<TableExpandIcon expanded={expanded} onExpand={onExpand} record={record} />
|
||||
);
|
||||
|
||||
const addNewPipelineHandler = useCallback((): void => {
|
||||
setActionType(ActionType.AddPipeline);
|
||||
}, [setActionType]);
|
||||
|
||||
const footer = useCallback((): JSX.Element | undefined => {
|
||||
if (isEditingActionMode) {
|
||||
return (
|
||||
<FooterButton
|
||||
type="link"
|
||||
onClick={addNewPipelineHandler}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('add_new_pipeline')}
|
||||
</FooterButton>
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}, [isEditingActionMode, addNewPipelineHandler, t]);
|
||||
|
||||
const onSaveConfigurationHandler = useCallback(async () => {
|
||||
const modifiedPipelineData = currPipelineData.map((item: PipelineData) => {
|
||||
const pipelineData = { ...item };
|
||||
delete pipelineData?.id;
|
||||
return pipelineData;
|
||||
});
|
||||
const response = await savePipeline({
|
||||
data: { pipelines: modifiedPipelineData },
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
refetchPipelineLists();
|
||||
setActionMode(ActionMode.Viewing);
|
||||
setShowSaveButton(undefined);
|
||||
setCurrPipelineData(response.payload?.pipelines);
|
||||
setPrevPipelineData(response.payload?.pipelines);
|
||||
} else {
|
||||
modifiedPipelineData.forEach((item: PipelineData) => {
|
||||
const pipelineData = item;
|
||||
pipelineData.id = v4();
|
||||
return pipelineData;
|
||||
});
|
||||
setActionMode(ActionMode.Editing);
|
||||
setShowSaveButton(ActionMode.Editing);
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('something_went_wrong'),
|
||||
});
|
||||
setCurrPipelineData(modifiedPipelineData);
|
||||
setPrevPipelineData(modifiedPipelineData);
|
||||
}
|
||||
}, [currPipelineData, notifications, refetchPipelineLists, setActionMode, t]);
|
||||
|
||||
const onCancelConfigurationHandler = useCallback((): void => {
|
||||
setActionMode(ActionMode.Viewing);
|
||||
setShowSaveButton(undefined);
|
||||
prevPipelineData.forEach((item, index) => {
|
||||
const obj = item;
|
||||
obj.orderId = index + 1;
|
||||
if (obj.config) {
|
||||
obj.config?.forEach((configItem, index) => {
|
||||
const config = configItem;
|
||||
config.orderId = index + 1;
|
||||
});
|
||||
for (let i = 0; i < obj.config.length - 1; i += 1) {
|
||||
obj.config[i].output = obj.config[i + 1].id;
|
||||
}
|
||||
}
|
||||
});
|
||||
setCurrPipelineData(prevPipelineData);
|
||||
setExpandedRowKeys([]);
|
||||
}, [prevPipelineData, setActionMode]);
|
||||
|
||||
const onRowHandler = (
|
||||
_data: PipelineData,
|
||||
index?: number,
|
||||
): React.HTMLAttributes<unknown> =>
|
||||
({
|
||||
index,
|
||||
moveRow: movePipelineRow,
|
||||
} as React.HTMLAttributes<unknown>);
|
||||
|
||||
const expandableConfig: ExpandableConfig<PipelineData> = {
|
||||
expandedRowKeys,
|
||||
onExpand,
|
||||
expandIcon: ({ expanded, onExpand, record }: ExpandRowConfig) =>
|
||||
getExpandIcon(expanded, onExpand, record),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
<AddNewPipeline
|
||||
isActionType={isActionType}
|
||||
setActionType={setActionType}
|
||||
selectedPipelineData={selectedPipelineData}
|
||||
setShowSaveButton={setShowSaveButton}
|
||||
setCurrPipelineData={setCurrPipelineData}
|
||||
currPipelineData={currPipelineData}
|
||||
/>
|
||||
<AddNewProcessor
|
||||
isActionType={isActionType}
|
||||
setActionType={setActionType}
|
||||
selectedProcessorData={selectedProcessorData}
|
||||
setShowSaveButton={setShowSaveButton}
|
||||
expandedPipelineData={expandedPipelineData()}
|
||||
setExpandedPipelineData={setExpandedPipelineData}
|
||||
/>
|
||||
<Container>
|
||||
<ModeAndConfiguration
|
||||
isActionMode={isActionMode}
|
||||
version={pipelineData?.version}
|
||||
/>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
expandedRowRender={expandedRowView}
|
||||
expandable={expandableConfig}
|
||||
components={tableComponents}
|
||||
dataSource={visibleCurrPipelines}
|
||||
onRow={onRowHandler}
|
||||
footer={footer}
|
||||
pagination={false}
|
||||
/>
|
||||
</DndProvider>
|
||||
{showSaveButton && (
|
||||
<SaveConfigButton
|
||||
onSaveConfigurationHandler={onSaveConfigurationHandler}
|
||||
onCancelConfigurationHandler={onCancelConfigurationHandler}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface PipelineListsViewProps {
|
||||
isActionType: string;
|
||||
setActionType: (actionType?: ActionType) => void;
|
||||
isActionMode: string;
|
||||
setActionMode: (actionMode: ActionMode) => void;
|
||||
pipelineData: Pipeline;
|
||||
refetchPipelineLists: VoidFunction;
|
||||
pipelineSearchValue: string;
|
||||
}
|
||||
|
||||
interface ExpandRowConfig {
|
||||
expanded: boolean;
|
||||
onExpand: (record: PipelineData, e: React.MouseEvent<HTMLElement>) => void;
|
||||
record: PipelineData;
|
||||
}
|
||||
|
||||
export interface AlertMessage {
|
||||
title: string;
|
||||
descrition: string;
|
||||
buttontext: string;
|
||||
onOk: VoidFunction;
|
||||
onCancel?: VoidFunction;
|
||||
}
|
||||
import PipelineListsView from './PipelineListsView';
|
||||
|
||||
export default PipelineListsView;
|
||||
|
@ -82,78 +82,42 @@ exports[`PipelinePage container test should render PipelinePageLayout section 1`
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="ant-input-group-wrapper ant-input-search css-dev-only-do-not-override-1i536d8"
|
||||
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<input
|
||||
class="ant-input css-dev-only-do-not-override-1i536d8"
|
||||
placeholder="search_pipeline_placeholder"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group css-dev-only-do-not-override-1i536d8"
|
||||
class="ant-input-suffix"
|
||||
>
|
||||
<span
|
||||
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
|
||||
class="ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<input
|
||||
class="ant-input css-dev-only-do-not-override-1i536d8"
|
||||
placeholder="search_pipeline_placeholder"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
>
|
||||
<span
|
||||
class="ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-dev-only-do-not-override-1i536d8 ant-btn-default ant-btn-icon-only ant-input-search-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
.c0 {
|
||||
|
@ -3,78 +3,42 @@
|
||||
exports[`PipelinePage container test should render PipelinesSearchSection section 1`] = `
|
||||
<DocumentFragment>
|
||||
<span
|
||||
class="ant-input-group-wrapper ant-input-search css-dev-only-do-not-override-1i536d8"
|
||||
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
|
||||
>
|
||||
<input
|
||||
class="ant-input css-dev-only-do-not-override-1i536d8"
|
||||
placeholder="search_pipeline_placeholder"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-input-wrapper ant-input-group css-dev-only-do-not-override-1i536d8"
|
||||
class="ant-input-suffix"
|
||||
>
|
||||
<span
|
||||
class="ant-input-affix-wrapper css-dev-only-do-not-override-1i536d8"
|
||||
class="ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<input
|
||||
class="ant-input css-dev-only-do-not-override-1i536d8"
|
||||
placeholder="search_pipeline_placeholder"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
>
|
||||
<span
|
||||
class="ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="ant-input-group-addon"
|
||||
>
|
||||
<button
|
||||
class="ant-btn css-dev-only-do-not-override-1i536d8 ant-btn-default ant-btn-icon-only ant-input-search-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Select, SelectProps, Space } from 'antd';
|
||||
import { getCategorySelectOptionByName } from 'container/NewWidget/RightContainer/alertFomatCategories';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { categoryToSupport } from './config';
|
||||
import { DefaultLabel, selectStyles } from './styles';
|
||||
@ -31,6 +32,7 @@ function BuilderUnitsFilter({
|
||||
<Space>
|
||||
<DefaultLabel>Y-axis unit</DefaultLabel>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
style={selectStyles}
|
||||
onChange={onChangeHandler}
|
||||
value={selectedValue}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { TableProps } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { DownloadOptions } from 'container/Download/Download.types';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { ReactNode } from 'react';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@ -14,4 +15,5 @@ export type QueryTableProps = Omit<
|
||||
renderActionCell?: (record: RowData) => ReactNode;
|
||||
modifyColumns?: (columns: ColumnsType<RowData>) => ColumnsType<RowData>;
|
||||
renderColumnCell?: Record<string, (record: RowData) => ReactNode>;
|
||||
downloadOption?: DownloadOptions;
|
||||
};
|
||||
|
9
frontend/src/container/QueryTable/QueryTable.styles.scss
Normal file
9
frontend/src/container/QueryTable/QueryTable.styles.scss
Normal file
@ -0,0 +1,9 @@
|
||||
.query-table {
|
||||
position: relative;
|
||||
.query-table--download {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
@ -1,8 +1,14 @@
|
||||
import './QueryTable.styles.scss';
|
||||
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import Download from 'container/Download/Download';
|
||||
import { IServiceName } from 'container/MetricsApplication/Tabs/types';
|
||||
import { createTableColumnsFromQuery } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { QueryTableProps } from './QueryTable.intefaces';
|
||||
import { createDownloadableData } from './utils';
|
||||
|
||||
export function QueryTable({
|
||||
queryTableData,
|
||||
@ -10,8 +16,12 @@ export function QueryTable({
|
||||
renderActionCell,
|
||||
modifyColumns,
|
||||
renderColumnCell,
|
||||
downloadOption,
|
||||
...props
|
||||
}: QueryTableProps): JSX.Element {
|
||||
const { isDownloadEnabled = false, fileName = '' } = downloadOption || {};
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
const { loading } = props;
|
||||
const { columns, dataSource } = useMemo(
|
||||
() =>
|
||||
createTableColumnsFromQuery({
|
||||
@ -23,16 +33,29 @@ export function QueryTable({
|
||||
[query, queryTableData, renderActionCell, renderColumnCell],
|
||||
);
|
||||
|
||||
const downloadableData = createDownloadableData(dataSource);
|
||||
|
||||
const tableColumns = modifyColumns ? modifyColumns(columns) : columns;
|
||||
|
||||
return (
|
||||
<ResizeTable
|
||||
columns={tableColumns}
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
scroll={{ x: true }}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
<div className="query-table">
|
||||
{isDownloadEnabled && (
|
||||
<div className="query-table--download">
|
||||
<Download
|
||||
data={downloadableData}
|
||||
fileName={`${fileName}-${servicename}`}
|
||||
isLoading={loading as boolean}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<ResizeTable
|
||||
columns={tableColumns}
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
scroll={{ x: true }}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
14
frontend/src/container/QueryTable/utils.ts
Normal file
14
frontend/src/container/QueryTable/utils.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
|
||||
export function createDownloadableData(
|
||||
inputData: RowData[],
|
||||
): Record<string, string>[] {
|
||||
return inputData.map((row) => ({
|
||||
Name: String(row.operation || ''),
|
||||
'P50 (in ns)': String(row.A || ''),
|
||||
'P90 (in ns)': String(row.B || ''),
|
||||
'P99 (in ns)': String(row.C || ''),
|
||||
'Number Of Calls': String(row.F || ''),
|
||||
'Error Rate (%)': String(row.F1 && row.F1 !== 'N/A' ? row.F1 : '0'),
|
||||
}));
|
||||
}
|
@ -7,13 +7,20 @@ import ROUTES from 'constants/routes';
|
||||
import useLicense, { LICENSE_PLAN_KEY } from 'hooks/useLicense';
|
||||
import history from 'lib/history';
|
||||
import { LifeBuoy } from 'lucide-react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { sideBarCollapse } from 'store/actions/app';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { checkVersionState, isCloudUser, isEECloudUser } from 'utils/app';
|
||||
|
||||
import { routeConfig, styles } from './config';
|
||||
@ -33,6 +40,7 @@ import {
|
||||
|
||||
function SideNav(): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
const [menuItems, setMenuItems] = useState(defaultMenuItems);
|
||||
const [collapsed, setCollapsed] = useState<boolean>(
|
||||
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||
);
|
||||
@ -44,36 +52,45 @@ function SideNav(): JSX.Element {
|
||||
featureResponse,
|
||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const { data } = useLicense();
|
||||
const { data, isFetching } = useLicense();
|
||||
|
||||
let secondaryMenuItems: MenuItem[] = [];
|
||||
|
||||
const isOnBasicPlan =
|
||||
data?.payload?.licenses?.some(
|
||||
(license) =>
|
||||
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
|
||||
) || data?.payload?.licenses === null;
|
||||
useEffect((): void => {
|
||||
const isOnboardingEnabled =
|
||||
featureResponse.data?.find(
|
||||
(feature) => feature.name === FeatureKeys.ONBOARDING,
|
||||
)?.active || false;
|
||||
|
||||
const menuItems = useMemo(
|
||||
() =>
|
||||
defaultMenuItems.filter((item) => {
|
||||
const isOnboardingEnabled =
|
||||
featureResponse.data?.find(
|
||||
(feature) => feature.name === FeatureKeys.ONBOARDING,
|
||||
)?.active || false;
|
||||
if (!isOnboardingEnabled || !isCloudUser()) {
|
||||
let items = [...menuItems];
|
||||
|
||||
if (role !== 'ADMIN' || isOnBasicPlan) {
|
||||
return item.key !== ROUTES.BILLING;
|
||||
}
|
||||
items = items.filter((item) => item.key !== ROUTES.GET_STARTED);
|
||||
|
||||
if (!isOnboardingEnabled || !isCloudUser()) {
|
||||
return item.key !== ROUTES.GET_STARTED;
|
||||
}
|
||||
setMenuItems(items);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [featureResponse.data]);
|
||||
|
||||
return true;
|
||||
}),
|
||||
[featureResponse.data, isOnBasicPlan, role],
|
||||
);
|
||||
// using a separate useEffect as the license fetching call takes few milliseconds
|
||||
useEffect(() => {
|
||||
if (!isFetching) {
|
||||
let items = [...menuItems];
|
||||
|
||||
const isOnBasicPlan =
|
||||
data?.payload?.licenses?.some(
|
||||
(license) =>
|
||||
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
|
||||
) || data?.payload?.licenses === null;
|
||||
|
||||
if (role !== USER_ROLES.ADMIN || isOnBasicPlan) {
|
||||
items = items.filter((item) => item.key !== ROUTES.BILLING);
|
||||
}
|
||||
|
||||
setMenuItems(items);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data?.payload?.licenses, isFetching, role]);
|
||||
|
||||
const { pathname, search } = useLocation();
|
||||
|
||||
@ -121,6 +138,7 @@ function SideNav(): JSX.Element {
|
||||
key: SecondaryMenuItemKey.Support,
|
||||
label: 'Support',
|
||||
icon: <LifeBuoy />,
|
||||
onClick: onClickMenuHandler,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
@ -172,7 +190,6 @@ function SideNav(): JSX.Element {
|
||||
mode="vertical"
|
||||
style={styles}
|
||||
items={secondaryMenuItems}
|
||||
onClick={onClickMenuHandler}
|
||||
/>
|
||||
</Sider>
|
||||
);
|
||||
|
@ -45,6 +45,6 @@ export const routeConfig: Record<string, QueryParams[]> = {
|
||||
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.PIPELINES]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.LOGS_PIPELINES]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.WORKSPACE_LOCKED]: [QueryParams.resourceAttributes],
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
LineChartOutlined,
|
||||
MenuOutlined,
|
||||
RocketOutlined,
|
||||
SearchOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -35,6 +36,18 @@ const menuItems: SidebarMenu[] = [
|
||||
key: ROUTES.LOGS_EXPLORER,
|
||||
label: 'Logs',
|
||||
icon: <AlignLeftOutlined />,
|
||||
children: [
|
||||
{
|
||||
key: ROUTES.LOGS_EXPLORER,
|
||||
icon: <SearchOutlined />,
|
||||
label: 'Logs Explorer',
|
||||
},
|
||||
{
|
||||
key: ROUTES.LOGS_PIPELINES,
|
||||
icon: <DeploymentUnitOutlined />,
|
||||
label: 'Logs Pipelines',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: ROUTES.ALL_DASHBOARD,
|
||||
|
@ -23,7 +23,7 @@ const breadcrumbNameMap = {
|
||||
[ROUTES.LOGS]: 'Logs',
|
||||
[ROUTES.LOGS_EXPLORER]: 'Logs Explorer',
|
||||
[ROUTES.LIVE_LOGS]: 'Live View',
|
||||
[ROUTES.PIPELINES]: 'Pipelines',
|
||||
[ROUTES.LOGS_PIPELINES]: 'Logs Pipelines',
|
||||
[ROUTES.BILLING]: 'Billing',
|
||||
[ROUTES.SUPPORT]: 'Support',
|
||||
[ROUTES.WORKSPACE_LOCKED]: 'Workspace Locked',
|
||||
|
@ -80,10 +80,7 @@ export const routesToSkip = [
|
||||
ROUTES.ORG_SETTINGS,
|
||||
ROUTES.INGESTION_SETTINGS,
|
||||
ROUTES.ERROR_DETAIL,
|
||||
ROUTES.ALERTS_NEW,
|
||||
ROUTES.EDIT_ALERTS,
|
||||
ROUTES.LIST_ALL_ALERT,
|
||||
ROUTES.PIPELINES,
|
||||
ROUTES.LOGS_PIPELINES,
|
||||
ROUTES.BILLING,
|
||||
ROUTES.SUPPORT,
|
||||
ROUTES.WORKSPACE_LOCKED,
|
||||
|
@ -34,8 +34,8 @@ function Version(): JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography.Title ellipsis level={4}>
|
||||
<Card style={{ margin: '16px 0' }}>
|
||||
<Typography.Title ellipsis level={4} style={{ marginTop: 0 }}>
|
||||
{t('version')}
|
||||
</Typography.Title>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
export const LICENSE_PLAN_KEY = {
|
||||
ENTERPRISE_PLAN: 'ENTERPRISE_PLAN',
|
||||
BASIC_PLAN: 'BASIC_PLAN ',
|
||||
BASIC_PLAN: 'BASIC_PLAN',
|
||||
};
|
||||
|
||||
export const LICENSE_PLAN_STATUS = {
|
||||
|
@ -7,6 +7,40 @@ export const licensesSuccessResponse = {
|
||||
workSpaceBlock: false,
|
||||
trialConvertedToSubscription: false,
|
||||
gracePeriodEnd: -1,
|
||||
licenses: [
|
||||
{
|
||||
key: 'testKeyId1',
|
||||
activationId: 'testActivationId1',
|
||||
ValidationMessage: '',
|
||||
isCurrent: false,
|
||||
planKey: 'ENTERPRISE_PLAN',
|
||||
ValidFrom: '2022-10-13T13:58:51Z',
|
||||
ValidUntil: '2023-10-13T19:57:37Z',
|
||||
status: 'VALID',
|
||||
},
|
||||
{
|
||||
key: 'testKeyId2',
|
||||
activationId: 'testActivationId2',
|
||||
ValidationMessage: '',
|
||||
isCurrent: true,
|
||||
planKey: 'ENTERPRISE_PLAN',
|
||||
ValidFrom: '2023-09-12T11:55:43Z',
|
||||
ValidUntil: '2024-09-11T17:34:29Z',
|
||||
status: 'VALID',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const licensesSuccessWorkspaceLockedResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
trialStart: 1695992049,
|
||||
trialEnd: 1697806449,
|
||||
onTrial: false,
|
||||
workSpaceBlock: true,
|
||||
trialConvertedToSubscription: false,
|
||||
gracePeriodEnd: -1,
|
||||
licenses: [
|
||||
{
|
||||
key: 'testKeyId1',
|
||||
|
@ -1,46 +1,70 @@
|
||||
import { licensesSuccessWorkspaceLockedResponse } from 'mocks-server/__mockdata__/licenses';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { act, render, screen } from 'tests/test-utils';
|
||||
|
||||
import WorkspaceLocked from '.';
|
||||
|
||||
describe('WorkspaceLocked', () => {
|
||||
const apiURL = 'http://localhost/api/v2/licenses';
|
||||
|
||||
test('Should render the component', async () => {
|
||||
server.use(
|
||||
rest.get(apiURL, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(licensesSuccessWorkspaceLockedResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
render(<WorkspaceLocked />);
|
||||
});
|
||||
const workspaceLocked = screen.getByRole('heading', {
|
||||
|
||||
const workspaceLocked = await screen.findByRole('heading', {
|
||||
name: /workspace locked/i,
|
||||
});
|
||||
expect(workspaceLocked).toBeInTheDocument();
|
||||
|
||||
const gotQuestionText = screen.getByText(/got question?/i);
|
||||
const gotQuestionText = await screen.findByText(/got question?/i);
|
||||
expect(gotQuestionText).toBeInTheDocument();
|
||||
|
||||
const contactUsLink = screen.getByRole('link', {
|
||||
const contactUsLink = await screen.findByRole('link', {
|
||||
name: /contact us/i,
|
||||
});
|
||||
expect(contactUsLink).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Render for Admin', async () => {
|
||||
server.use(
|
||||
rest.get(apiURL, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(licensesSuccessWorkspaceLockedResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<WorkspaceLocked />);
|
||||
const contactAdminMessage = screen.queryByText(
|
||||
const contactAdminMessage = await screen.queryByText(
|
||||
/please contact your administrator for further help/i,
|
||||
);
|
||||
expect(contactAdminMessage).not.toBeInTheDocument();
|
||||
const updateCreditCardBtn = screen.getByRole('button', {
|
||||
const updateCreditCardBtn = await screen.findByRole('button', {
|
||||
name: /update credit card/i,
|
||||
});
|
||||
expect(updateCreditCardBtn).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Render for non Admin', async () => {
|
||||
server.use(
|
||||
rest.get(apiURL, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(licensesSuccessWorkspaceLockedResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<WorkspaceLocked />, {}, 'VIEWER');
|
||||
const updateCreditCardBtn = screen.queryByRole('button', {
|
||||
const updateCreditCardBtn = await screen.queryByRole('button', {
|
||||
name: /update credit card/i,
|
||||
});
|
||||
expect(updateCreditCardBtn).not.toBeInTheDocument();
|
||||
|
||||
const contactAdminMessage = screen.getByText(
|
||||
const contactAdminMessage = await screen.findByText(
|
||||
/please contact your administrator for further help/i,
|
||||
);
|
||||
expect(contactAdminMessage).toBeInTheDocument();
|
||||
|
@ -2,11 +2,13 @@
|
||||
import './WorkspaceLocked.styles.scss';
|
||||
|
||||
import { CreditCardOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Typography } from 'antd';
|
||||
import { Button, Card, Skeleton, Typography } from 'antd';
|
||||
import updateCreditCardApi from 'api/billing/checkout';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useLicense from 'hooks/useLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -22,16 +24,28 @@ export default function WorkspaceBlocked(): JSX.Element {
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { isFetching, data: licensesData } = useLicense();
|
||||
const {
|
||||
isFetching: isFetchingLicenseData,
|
||||
isLoading: isLoadingLicenseData,
|
||||
data: licensesData,
|
||||
} = useLicense();
|
||||
|
||||
useEffect(() => {
|
||||
const activeValidLicense =
|
||||
licensesData?.payload?.licenses?.find(
|
||||
(license) => license.isCurrent === true,
|
||||
) || null;
|
||||
if (!isFetchingLicenseData) {
|
||||
const shouldBlockWorkspace = licensesData?.payload?.workSpaceBlock;
|
||||
|
||||
setActiveLicense(activeValidLicense);
|
||||
}, [isFetching, licensesData]);
|
||||
if (!shouldBlockWorkspace) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
|
||||
const activeValidLicense =
|
||||
licensesData?.payload?.licenses?.find(
|
||||
(license) => license.isCurrent === true,
|
||||
) || null;
|
||||
|
||||
setActiveLicense(activeValidLicense);
|
||||
}
|
||||
}, [isFetchingLicenseData, licensesData]);
|
||||
|
||||
const { mutate: updateCreditCard, isLoading } = useMutation(
|
||||
updateCreditCardApi,
|
||||
@ -62,36 +76,41 @@ export default function WorkspaceBlocked(): JSX.Element {
|
||||
|
||||
return (
|
||||
<Card className="workspace-locked-container">
|
||||
<LockOutlined style={{ fontSize: '36px', color: '#08c' }} />
|
||||
<Typography.Title level={4}> Workspace Locked </Typography.Title>
|
||||
|
||||
<Typography.Paragraph className="workpace-locked-details">
|
||||
You have been locked out of your workspace because your trial ended without
|
||||
an upgrade to a paid plan. Your data will continue to be ingested till{' '}
|
||||
{getFormattedDate(licensesData?.payload?.gracePeriodEnd || Date.now())} , at
|
||||
which point we will drop all the ingested data and terminate the account.
|
||||
{!isAdmin && 'Please contact your administrator for further help'}
|
||||
</Typography.Paragraph>
|
||||
|
||||
{isAdmin && (
|
||||
<Button
|
||||
className="update-credit-card-btn"
|
||||
type="primary"
|
||||
icon={<CreditCardOutlined />}
|
||||
size="middle"
|
||||
loading={isLoading}
|
||||
onClick={handleUpdateCreditCard}
|
||||
>
|
||||
Update Credit Card
|
||||
</Button>
|
||||
{isLoadingLicenseData || !licensesData?.payload?.workSpaceBlock ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
<>
|
||||
<LockOutlined style={{ fontSize: '36px', color: '#08c' }} />
|
||||
<Typography.Title level={4}> Workspace Locked </Typography.Title>
|
||||
<Typography.Paragraph className="workpace-locked-details">
|
||||
You have been locked out of your workspace because your trial ended
|
||||
without an upgrade to a paid plan. Your data will continue to be ingested
|
||||
till{' '}
|
||||
{getFormattedDate(licensesData?.payload?.gracePeriodEnd || Date.now())} ,
|
||||
at which point we will drop all the ingested data and terminate the
|
||||
account.
|
||||
{!isAdmin && 'Please contact your administrator for further help'}
|
||||
</Typography.Paragraph>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
className="update-credit-card-btn"
|
||||
type="primary"
|
||||
icon={<CreditCardOutlined />}
|
||||
size="middle"
|
||||
loading={isLoading}
|
||||
onClick={handleUpdateCreditCard}
|
||||
>
|
||||
Update Credit Card
|
||||
</Button>
|
||||
)}
|
||||
<div className="contact-us">
|
||||
Got Questions?
|
||||
<span>
|
||||
<a href="mailto:support@signoz.io"> Contact Us </a>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="contact-us">
|
||||
Got Questions?
|
||||
<span>
|
||||
<a href="mailto:support@signoz.io"> Contact Us </a>
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ export interface GettableAlert extends AlertDef {
|
||||
alert: string;
|
||||
state: string;
|
||||
disabled: boolean;
|
||||
createAt: string;
|
||||
createBy: string;
|
||||
updateAt: string;
|
||||
updateBy: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = {
|
||||
|
@ -42,6 +42,8 @@ export interface Dashboard {
|
||||
uuid: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by: string;
|
||||
updated_by: string;
|
||||
data: DashboardData;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,17 @@ export interface ProcessorData {
|
||||
on_error?: string;
|
||||
field?: string;
|
||||
value?: string;
|
||||
|
||||
// trace parser fields.
|
||||
trace_id?: {
|
||||
parse_from: string;
|
||||
};
|
||||
span_id?: {
|
||||
parse_from: string;
|
||||
};
|
||||
trace_flags?: {
|
||||
parse_from: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PipelineData {
|
||||
|
@ -3,3 +3,9 @@ export type VIEWER = 'VIEWER';
|
||||
export type EDITOR = 'EDITOR';
|
||||
|
||||
export type ROLES = ADMIN | VIEWER | EDITOR;
|
||||
|
||||
export const USER_ROLES = {
|
||||
ADMIN: 'ADMIN',
|
||||
VIEWER: 'VIEWER',
|
||||
EDITOR: 'EDITOR',
|
||||
};
|
||||
|
@ -75,9 +75,8 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
LIVE_LOGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LIST_LICENSES: ['ADMIN'],
|
||||
LOGS_INDEX_FIELDS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS_PIPELINE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS_PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
GET_STARTED: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
WORKSPACE_LOCKED: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
BILLING: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
|
@ -218,6 +218,16 @@
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/generator@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
|
||||
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
|
||||
dependencies:
|
||||
"@babel/types" "^7.23.0"
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/helper-annotate-as-pure@^7.15.4", "@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz"
|
||||
@ -354,6 +364,11 @@
|
||||
resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz"
|
||||
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
|
||||
|
||||
"@babel/helper-environment-visitor@^7.22.20":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
|
||||
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
|
||||
|
||||
"@babel/helper-environment-visitor@^7.22.5":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
|
||||
@ -382,6 +397,14 @@
|
||||
"@babel/template" "^7.22.5"
|
||||
"@babel/types" "^7.22.5"
|
||||
|
||||
"@babel/helper-function-name@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
|
||||
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
|
||||
dependencies:
|
||||
"@babel/template" "^7.22.15"
|
||||
"@babel/types" "^7.23.0"
|
||||
|
||||
"@babel/helper-hoist-variables@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz"
|
||||
@ -593,6 +616,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044"
|
||||
integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.22.20":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
|
||||
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.22.5":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
|
||||
@ -692,6 +720,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.15.tgz#d34592bfe288a32e741aa0663dbc4829fcd55160"
|
||||
integrity sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA==
|
||||
|
||||
"@babel/parser@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
|
||||
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz"
|
||||
@ -2047,51 +2080,19 @@
|
||||
"@babel/parser" "^7.22.5"
|
||||
"@babel/types" "^7.22.5"
|
||||
|
||||
"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
|
||||
version "7.21.4"
|
||||
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz"
|
||||
integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.21.4"
|
||||
"@babel/generator" "^7.21.4"
|
||||
"@babel/helper-environment-visitor" "^7.18.9"
|
||||
"@babel/helper-function-name" "^7.21.0"
|
||||
"@babel/helper-hoist-variables" "^7.18.6"
|
||||
"@babel/helper-split-export-declaration" "^7.18.6"
|
||||
"@babel/parser" "^7.21.4"
|
||||
"@babel/types" "^7.21.4"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/traverse@^7.22.11":
|
||||
version "7.22.11"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.11.tgz#71ebb3af7a05ff97280b83f05f8865ac94b2027c"
|
||||
integrity sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.22.10"
|
||||
"@babel/generator" "^7.22.10"
|
||||
"@babel/helper-environment-visitor" "^7.22.5"
|
||||
"@babel/helper-function-name" "^7.22.5"
|
||||
"@babel/helper-hoist-variables" "^7.22.5"
|
||||
"@babel/helper-split-export-declaration" "^7.22.6"
|
||||
"@babel/parser" "^7.22.11"
|
||||
"@babel/types" "^7.22.11"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/traverse@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.15.tgz#75be4d2d6e216e880e93017f4e2389aeb77ef2d9"
|
||||
integrity sha512-DdHPwvJY0sEeN4xJU5uRLmZjgMMDIvMPniLuYzUVXj/GGzysPl0/fwt44JBkyUIzGJPV8QgHMcQdQ34XFuKTYQ==
|
||||
"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.22.11", "@babel/traverse@^7.22.15", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
|
||||
version "7.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
|
||||
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.22.13"
|
||||
"@babel/generator" "^7.22.15"
|
||||
"@babel/helper-environment-visitor" "^7.22.5"
|
||||
"@babel/helper-function-name" "^7.22.5"
|
||||
"@babel/generator" "^7.23.0"
|
||||
"@babel/helper-environment-visitor" "^7.22.20"
|
||||
"@babel/helper-function-name" "^7.23.0"
|
||||
"@babel/helper-hoist-variables" "^7.22.5"
|
||||
"@babel/helper-split-export-declaration" "^7.22.6"
|
||||
"@babel/parser" "^7.22.15"
|
||||
"@babel/types" "^7.22.15"
|
||||
"@babel/parser" "^7.23.0"
|
||||
"@babel/types" "^7.23.0"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
@ -2122,6 +2123,15 @@
|
||||
"@babel/helper-validator-identifier" "^7.22.15"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
|
||||
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.22.5"
|
||||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@bcoe/v8-coverage@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
|
||||
|
14
go.mod
14
go.mod
@ -5,6 +5,7 @@ go 1.21
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.14.0
|
||||
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb
|
||||
github.com/SigNoz/signoz-otel-collector v0.79.12
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
|
||||
github.com/antonmedv/expr v1.12.5
|
||||
@ -64,7 +65,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
google.golang.org/grpc v1.57.0
|
||||
google.golang.org/grpc v1.57.1
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/segmentio/analytics-go.v3 v3.1.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@ -93,7 +94,7 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.6.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
@ -147,17 +148,18 @@ require (
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/segmentio/backo-go v1.0.1 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.4 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.5 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.2 // indirect
|
||||
github.com/smarty/assertions v1.15.0 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/vjeantet/grok v1.0.1 // indirect
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/collector/featuregate v1.0.0-rcv0012 // indirect
|
||||
go.opentelemetry.io/collector/semconv v0.81.0 // indirect
|
||||
|
72
go.sum
72
go.sum
@ -99,6 +99,8 @@ github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb h1:bneLSKPf9YUSFm
|
||||
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb/go.mod h1:JznGDNg9x1cujDKa22RaQOimOvvEfy3nxzDGd8XDgmA=
|
||||
github.com/SigNoz/prometheus v1.9.78 h1:bB3yuDrRzi/Mv00kWayR9DZbyjTuGfendSqISyDcXiY=
|
||||
github.com/SigNoz/prometheus v1.9.78/go.mod h1:MffmFu2qFILQrOHehx3D0XjYtaZMVfI+Ppeiv98x4Ww=
|
||||
github.com/SigNoz/signoz-otel-collector v0.79.12 h1:0yDMhcN7Taa8WrFv8YrHRaDvRxHqLfp5c6w1TSEWk+I=
|
||||
github.com/SigNoz/signoz-otel-collector v0.79.12/go.mod h1:MXjHt3atjTAF2Wrqu0W7Xx+oJ1yb8UfpsNu+A8Ssjtg=
|
||||
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=
|
||||
github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo=
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=
|
||||
@ -196,8 +198,8 @@ github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m3
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
|
||||
github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11 h1:IPrmumsT9t5BS7XcPhgsCTlkWbYg80SEXUzDpReaU6Y=
|
||||
github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11/go.mod h1:a6bNUGTbQBsY6VRHTr4h/rkOXjl244DyRD0tx3fgq4Q=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
@ -205,8 +207,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -229,8 +231,9 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8=
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
@ -270,8 +273,8 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
|
||||
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
@ -280,8 +283,9 @@ github.com/go-redis/redismock/v8 v8.11.5 h1:RJFIiua58hrBrSpXhnGX3on79AU3S271H4Zh
|
||||
github.com/go-redis/redismock/v8 v8.11.5/go.mod h1:UaAU9dEe1C+eGr+FHV5prCWIt0hafyPWbGMEWE0UWdA=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
@ -337,8 +341,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
|
||||
github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@ -358,8 +362,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@ -443,8 +447,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
@ -472,6 +476,7 @@ github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoI
|
||||
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hetznercloud/hcloud-go v1.41.0 h1:KJGFRRc68QiVu4PrEP5BmCQVveCP2CM26UGQUKGpIUs=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.0.0 h1:Sg1DJ+MAKvbYAqaBaq9tPbwXBS2ckPIaMtVdUjKu+4g=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.0.0/go.mod h1:4iUG2NG8b61IAwNx6UsMWQ6IfIf/i1RsG0BbsKAyR5Q=
|
||||
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
|
||||
@ -479,8 +484,8 @@ github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEF
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.1.8 h1:493wE/BkZxJf7x79UCE0cYGPZoqQcPiEBALvt7uVGY0=
|
||||
@ -537,8 +542,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/linode/linodego v1.19.0 h1:n4WJrcr9+30e9JGZ6DI0nZbm5SdAj1kSwvvt/998YUw=
|
||||
github.com/linode/linodego v1.19.0/go.mod h1:XZFR+yJ9mm2kwf6itZ6SCpu+6w3KnIevV0Uu5HNWJgQ=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
@ -750,12 +756,12 @@ github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N+
|
||||
github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc=
|
||||
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
|
||||
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5/go.mod h1:Ng3Maa27Q2KARVJ0SPZF5NdrQSC3XHKP8IIWrHgMeLY=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
@ -763,8 +769,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@ -777,8 +783,9 @@ github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sS
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@ -800,6 +807,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
|
||||
@ -811,6 +819,8 @@ github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ
|
||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/vjeantet/grok v1.0.1 h1:2rhIR7J4gThTgcZ1m2JY4TrJZNgjn985U28kT2wQrJ4=
|
||||
github.com/vjeantet/grok v1.0.1/go.mod h1:ax1aAchzC6/QMXMcyzHQGZWaW1l195+uMYIkCWPCNIo=
|
||||
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
||||
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
@ -825,8 +835,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
||||
@ -1136,7 +1146,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@ -1395,8 +1405,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg=
|
||||
google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
@ -1,5 +1,5 @@
|
||||
# use a minimal alpine image
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.18.3
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/agentConf/sqlite"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -61,6 +62,13 @@ func (r *Repo) GetConfigHistory(
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
incompleteStatuses := []DeployStatus{DeployInitiated, Deploying}
|
||||
for idx := 1; idx < len(c); idx++ {
|
||||
if slices.Contains(incompleteStatuses, c[idx].DeployStatus) {
|
||||
c[idx].DeployStatus = DeployStatusUnknown
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,12 @@ const (
|
||||
type DeployStatus string
|
||||
|
||||
const (
|
||||
PendingDeploy DeployStatus = "DIRTY"
|
||||
Deploying DeployStatus = "DEPLOYING"
|
||||
Deployed DeployStatus = "DEPLOYED"
|
||||
DeployInitiated DeployStatus = "IN_PROGRESS"
|
||||
DeployFailed DeployStatus = "FAILED"
|
||||
PendingDeploy DeployStatus = "DIRTY"
|
||||
Deploying DeployStatus = "DEPLOYING"
|
||||
Deployed DeployStatus = "DEPLOYED"
|
||||
DeployInitiated DeployStatus = "IN_PROGRESS"
|
||||
DeployFailed DeployStatus = "FAILED"
|
||||
DeployStatusUnknown DeployStatus = "UNKNOWN"
|
||||
)
|
||||
|
||||
type ConfigVersion struct {
|
||||
|
@ -3493,10 +3493,16 @@ func (r *ClickHouseReader) UpdateLogField(ctx context.Context, field *model.Upda
|
||||
return &model.ApiError{Err: err, Typ: model.ErrorInternal}
|
||||
}
|
||||
|
||||
query = fmt.Sprintf("ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS %s %s MATERIALIZED -1",
|
||||
defaultValueDistributed := "-1"
|
||||
if strings.ToLower(field.DataType) == "bool" {
|
||||
defaultValueDistributed = "false"
|
||||
field.IndexType = "set(2)"
|
||||
}
|
||||
query = fmt.Sprintf("ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS %s %s MATERIALIZED %s",
|
||||
r.logsDB, r.logsTable,
|
||||
r.cluster,
|
||||
colname, field.DataType,
|
||||
defaultValueDistributed,
|
||||
)
|
||||
err = r.db.Exec(ctx, query)
|
||||
if err != nil {
|
||||
|
@ -140,7 +140,7 @@ func (ic *LogParsingPipelineController) PreviewLogsPipelines(
|
||||
ctx context.Context,
|
||||
request *PipelinesPreviewRequest,
|
||||
) (*PipelinesPreviewResponse, *model.ApiError) {
|
||||
result, err := SimulatePipelinesProcessing(
|
||||
result, _, err := SimulatePipelinesProcessing(
|
||||
ctx, request.Pipelines, request.Logs,
|
||||
)
|
||||
|
||||
|
@ -41,6 +41,7 @@ type PipelineOperator struct {
|
||||
ID string `json:"id,omitempty" yaml:"id,omitempty"`
|
||||
Output string `json:"output,omitempty" yaml:"output,omitempty"`
|
||||
OnError string `json:"on_error,omitempty" yaml:"on_error,omitempty"`
|
||||
If string `json:"if,omitempty" yaml:"if,omitempty"`
|
||||
|
||||
// don't need the following in the final config
|
||||
OrderId int `json:"orderId" yaml:"-"`
|
||||
@ -48,20 +49,20 @@ type PipelineOperator struct {
|
||||
Name string `json:"name,omitempty" yaml:"-"`
|
||||
|
||||
// optional keys depending on the type
|
||||
ParseTo string `json:"parse_to,omitempty" yaml:"parse_to,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
|
||||
Regex string `json:"regex,omitempty" yaml:"regex,omitempty"`
|
||||
ParseFrom string `json:"parse_from,omitempty" yaml:"parse_from,omitempty"`
|
||||
Timestamp *TimestampParser `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
|
||||
TraceParser *TraceParser `json:"trace_parser,omitempty" yaml:"trace_parser,omitempty"`
|
||||
Field string `json:"field,omitempty" yaml:"field,omitempty"`
|
||||
Value string `json:"value,omitempty" yaml:"value,omitempty"`
|
||||
From string `json:"from,omitempty" yaml:"from,omitempty"`
|
||||
To string `json:"to,omitempty" yaml:"to,omitempty"`
|
||||
Expr string `json:"expr,omitempty" yaml:"expr,omitempty"`
|
||||
Routes *[]Route `json:"routes,omitempty" yaml:"routes,omitempty"`
|
||||
Fields []string `json:"fields,omitempty" yaml:"fields,omitempty"`
|
||||
Default string `json:"default,omitempty" yaml:"default,omitempty"`
|
||||
ParseTo string `json:"parse_to,omitempty" yaml:"parse_to,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
|
||||
Regex string `json:"regex,omitempty" yaml:"regex,omitempty"`
|
||||
ParseFrom string `json:"parse_from,omitempty" yaml:"parse_from,omitempty"`
|
||||
Timestamp *TimestampParser `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
|
||||
*TraceParser `yaml:",inline,omitempty"`
|
||||
Field string `json:"field,omitempty" yaml:"field,omitempty"`
|
||||
Value string `json:"value,omitempty" yaml:"value,omitempty"`
|
||||
From string `json:"from,omitempty" yaml:"from,omitempty"`
|
||||
To string `json:"to,omitempty" yaml:"to,omitempty"`
|
||||
Expr string `json:"expr,omitempty" yaml:"expr,omitempty"`
|
||||
Routes *[]Route `json:"routes,omitempty" yaml:"routes,omitempty"`
|
||||
Fields []string `json:"fields,omitempty" yaml:"fields,omitempty"`
|
||||
Default string `json:"default,omitempty" yaml:"default,omitempty"`
|
||||
}
|
||||
|
||||
type TimestampParser struct {
|
||||
|
@ -1,6 +1,9 @@
|
||||
package logparsingpipeline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/queryBuilderToExpr"
|
||||
@ -72,6 +75,39 @@ func getOperators(ops []PipelineOperator) []PipelineOperator {
|
||||
if len(filteredOp) > 0 {
|
||||
filteredOp[len(filteredOp)-1].Output = operator.ID
|
||||
}
|
||||
|
||||
if operator.Type == "regex_parser" {
|
||||
parseFromParts := strings.Split(operator.ParseFrom, ".")
|
||||
parseFromPath := strings.Join(parseFromParts, "?.")
|
||||
operator.If = fmt.Sprintf(
|
||||
`%s != nil && %s matches "%s"`,
|
||||
parseFromPath,
|
||||
parseFromPath,
|
||||
strings.ReplaceAll(
|
||||
strings.ReplaceAll(operator.Regex, `\`, `\\`),
|
||||
`"`, `\"`,
|
||||
),
|
||||
)
|
||||
|
||||
} else if operator.Type == "json_parser" {
|
||||
parseFromParts := strings.Split(operator.ParseFrom, ".")
|
||||
parseFromPath := strings.Join(parseFromParts, "?.")
|
||||
operator.If = fmt.Sprintf(`%s != nil && %s matches "^\\s*{.*}\\s*$"`, parseFromPath, parseFromPath)
|
||||
|
||||
} else if operator.Type == "move" || operator.Type == "copy" {
|
||||
fromParts := strings.Split(operator.From, ".")
|
||||
fromPath := strings.Join(fromParts, "?.")
|
||||
operator.If = fmt.Sprintf(`%s != nil`, fromPath)
|
||||
|
||||
} else if operator.Type == "remove" {
|
||||
fieldParts := strings.Split(operator.Field, ".")
|
||||
fieldPath := strings.Join(fieldParts, "?.")
|
||||
operator.If = fmt.Sprintf(`%s != nil`, fieldPath)
|
||||
|
||||
} else if operator.Type == "trace_parser" {
|
||||
cleanTraceParser(&operator)
|
||||
}
|
||||
|
||||
filteredOp = append(filteredOp, operator)
|
||||
} else if i == len(ops)-1 && len(filteredOp) != 0 {
|
||||
filteredOp[len(filteredOp)-1].Output = ""
|
||||
@ -79,3 +115,15 @@ func getOperators(ops []PipelineOperator) []PipelineOperator {
|
||||
}
|
||||
return filteredOp
|
||||
}
|
||||
|
||||
func cleanTraceParser(operator *PipelineOperator) {
|
||||
if operator.TraceId != nil && len(operator.TraceId.ParseFrom) < 1 {
|
||||
operator.TraceId = nil
|
||||
}
|
||||
if operator.SpanId != nil && len(operator.SpanId.ParseFrom) < 1 {
|
||||
operator.SpanId = nil
|
||||
}
|
||||
if operator.TraceFlags != nil && len(operator.TraceFlags.ParseFrom) < 1 {
|
||||
operator.TraceFlags = nil
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,17 @@
|
||||
package logparsingpipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/entry"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
)
|
||||
|
||||
var prepareProcessorTestData = []struct {
|
||||
@ -195,3 +203,161 @@ func TestPreparePipelineProcessor(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoCollectorErrorsFromProcessorsForMismatchedLogs(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testPipelineFilter := &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "method",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
Operator: "=",
|
||||
Value: "GET",
|
||||
},
|
||||
},
|
||||
}
|
||||
makeTestPipeline := func(config []PipelineOperator) Pipeline {
|
||||
return Pipeline{
|
||||
OrderId: 1,
|
||||
Name: "pipeline1",
|
||||
Alias: "pipeline1",
|
||||
Enabled: true,
|
||||
Filter: testPipelineFilter,
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
|
||||
makeTestLog := func(
|
||||
body string,
|
||||
attributes map[string]string,
|
||||
) model.SignozLog {
|
||||
attributes["method"] = "GET"
|
||||
|
||||
testTraceId, err := utils.RandomHex(16)
|
||||
require.Nil(err)
|
||||
|
||||
testSpanId, err := utils.RandomHex(8)
|
||||
require.Nil(err)
|
||||
|
||||
return model.SignozLog{
|
||||
Timestamp: uint64(time.Now().UnixNano()),
|
||||
Body: body,
|
||||
Attributes_string: attributes,
|
||||
Resources_string: attributes,
|
||||
SeverityText: entry.Info.String(),
|
||||
SeverityNumber: uint8(entry.Info),
|
||||
SpanID: testSpanId,
|
||||
TraceID: testTraceId,
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Operator PipelineOperator
|
||||
NonMatchingLog model.SignozLog
|
||||
}{
|
||||
{
|
||||
"regex processor should ignore log with missing field",
|
||||
PipelineOperator{
|
||||
ID: "regex",
|
||||
Type: "regex_parser",
|
||||
Enabled: true,
|
||||
Name: "regex parser",
|
||||
ParseFrom: "attributes.test_regex_target",
|
||||
ParseTo: "attributes",
|
||||
Regex: `^\s*(?P<json_data>{.*})\s*$`,
|
||||
},
|
||||
makeTestLog("mismatching log", map[string]string{}),
|
||||
}, {
|
||||
"regex processor should ignore non-matching log",
|
||||
PipelineOperator{
|
||||
ID: "regex",
|
||||
Type: "regex_parser",
|
||||
Enabled: true,
|
||||
Name: "regex parser",
|
||||
ParseFrom: "body",
|
||||
ParseTo: "attributes",
|
||||
Regex: `^\s*(?P<body_json>{.*})\s*$`,
|
||||
},
|
||||
makeTestLog("mismatching log", map[string]string{}),
|
||||
}, {
|
||||
"json parser should ignore logs with missing field.",
|
||||
PipelineOperator{
|
||||
ID: "json",
|
||||
Type: "json_parser",
|
||||
Enabled: true,
|
||||
Name: "json parser",
|
||||
ParseFrom: "attributes.test_json",
|
||||
ParseTo: "attributes",
|
||||
},
|
||||
makeTestLog("mismatching log", map[string]string{}),
|
||||
},
|
||||
{
|
||||
"json parser should ignore log with non JSON target field value",
|
||||
PipelineOperator{
|
||||
ID: "json",
|
||||
Type: "json_parser",
|
||||
Enabled: true,
|
||||
Name: "json parser",
|
||||
ParseFrom: "attributes.test_json",
|
||||
ParseTo: "attributes",
|
||||
},
|
||||
makeTestLog("mismatching log", map[string]string{
|
||||
"test_json": "bad json",
|
||||
}),
|
||||
}, {
|
||||
"move parser should ignore non matching logs",
|
||||
PipelineOperator{
|
||||
ID: "move",
|
||||
Type: "move",
|
||||
Enabled: true,
|
||||
Name: "move",
|
||||
From: "attributes.test1",
|
||||
To: "attributes.test2",
|
||||
},
|
||||
makeTestLog("mismatching log", map[string]string{}),
|
||||
}, {
|
||||
"copy parser should ignore non matching logs",
|
||||
PipelineOperator{
|
||||
ID: "copy",
|
||||
Type: "copy",
|
||||
Enabled: true,
|
||||
Name: "copy",
|
||||
From: "attributes.test1",
|
||||
To: "attributes.test2",
|
||||
},
|
||||
makeTestLog("mismatching log", map[string]string{}),
|
||||
}, {
|
||||
"remove parser should ignore non matching logs",
|
||||
PipelineOperator{
|
||||
ID: "remove",
|
||||
Type: "remove",
|
||||
Enabled: true,
|
||||
Name: "remove",
|
||||
Field: "attributes.test",
|
||||
},
|
||||
makeTestLog("mismatching log", map[string]string{}),
|
||||
},
|
||||
// TODO(Raj): see if there is an error scenario for grok parser.
|
||||
// TODO(Raj): see if there is an error scenario for trace parser.
|
||||
// TODO(Raj): see if there is an error scenario for Add operator.
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testPipelines := []Pipeline{makeTestPipeline([]PipelineOperator{testCase.Operator})}
|
||||
|
||||
result, collectorErrorLogs, err := SimulatePipelinesProcessing(
|
||||
context.Background(),
|
||||
testPipelines,
|
||||
[]model.SignozLog{testCase.NonMatchingLog},
|
||||
)
|
||||
require.Nil(err)
|
||||
require.Equal(0, len(collectorErrorLogs), strings.Join(collectorErrorLogs, "\n"))
|
||||
require.Equal(1, len(result))
|
||||
}
|
||||
}
|
||||
|
@ -137,20 +137,35 @@ func isValidOperator(op PipelineOperator) error {
|
||||
if op.Field == "" {
|
||||
return fmt.Errorf(fmt.Sprintf("field of %s remove operator cannot be empty", op.ID))
|
||||
}
|
||||
case "traceParser":
|
||||
case "trace_parser":
|
||||
if op.TraceParser == nil {
|
||||
return fmt.Errorf(fmt.Sprintf("field of %s remove operator cannot be empty", op.ID))
|
||||
}
|
||||
|
||||
if op.TraceParser.SpanId.ParseFrom == "" && op.TraceParser.TraceId.ParseFrom == "" && op.TraceParser.TraceFlags.ParseFrom == "" {
|
||||
return fmt.Errorf(fmt.Sprintf("one of trace_id,span_id,parse_from of %s traceParser operator must be present", op.ID))
|
||||
hasTraceIdParseFrom := (op.TraceParser.TraceId != nil && op.TraceParser.TraceId.ParseFrom != "")
|
||||
hasSpanIdParseFrom := (op.TraceParser.SpanId != nil && op.TraceParser.SpanId.ParseFrom != "")
|
||||
hasTraceFlagsParseFrom := (op.TraceParser.TraceFlags != nil && op.TraceParser.TraceFlags.ParseFrom != "")
|
||||
|
||||
if !(hasTraceIdParseFrom || hasSpanIdParseFrom || hasTraceFlagsParseFrom) {
|
||||
return fmt.Errorf(fmt.Sprintf("one of trace_id, span_id, trace_flags of %s trace_parser operator must be present", op.ID))
|
||||
}
|
||||
|
||||
if hasTraceIdParseFrom && !isValidOtelValue(op.TraceParser.TraceId.ParseFrom) {
|
||||
return fmt.Errorf("trace id can't be parsed from %s", op.TraceParser.TraceId.ParseFrom)
|
||||
}
|
||||
if hasSpanIdParseFrom && !isValidOtelValue(op.TraceParser.SpanId.ParseFrom) {
|
||||
return fmt.Errorf("span id can't be parsed from %s", op.TraceParser.SpanId.ParseFrom)
|
||||
}
|
||||
if hasTraceFlagsParseFrom && !isValidOtelValue(op.TraceParser.TraceFlags.ParseFrom) {
|
||||
return fmt.Errorf("trace flags can't be parsed from %s", op.TraceParser.TraceFlags.ParseFrom)
|
||||
}
|
||||
|
||||
case "retain":
|
||||
if len(op.Fields) == 0 {
|
||||
return fmt.Errorf(fmt.Sprintf("fields of %s retain operator cannot be empty", op.ID))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(fmt.Sprintf("operator type %s not supported for %s, use one of (grok_parser, regex_parser, copy, move, add, remove, traceParser, retain)", op.Type, op.ID))
|
||||
return fmt.Errorf(fmt.Sprintf("operator type %s not supported for %s, use one of (grok_parser, regex_parser, copy, move, add, remove, trace_parser, retain)", op.Type, op.ID))
|
||||
}
|
||||
|
||||
if !isValidOtelValue(op.ParseFrom) ||
|
||||
|
@ -250,6 +250,31 @@ var operatorTest = []struct {
|
||||
ParseTo: "attributes",
|
||||
},
|
||||
IsValid: false,
|
||||
}, {
|
||||
Name: "Trace Parser - invalid - no trace_parser spec",
|
||||
Operator: PipelineOperator{
|
||||
ID: "trace",
|
||||
Type: "trace_parser",
|
||||
},
|
||||
IsValid: false,
|
||||
}, {
|
||||
Name: "Trace Parser - invalid - no ParseFrom specified",
|
||||
Operator: PipelineOperator{
|
||||
ID: "trace",
|
||||
Type: "trace_parser",
|
||||
TraceParser: &TraceParser{},
|
||||
},
|
||||
IsValid: false,
|
||||
}, {
|
||||
Name: "Trace Parser - invalid - bad parsefrom attribute",
|
||||
Operator: PipelineOperator{
|
||||
ID: "trace",
|
||||
Type: "trace_parser",
|
||||
TraceParser: &TraceParser{
|
||||
TraceId: &ParseFrom{ParseFrom: "trace_id"},
|
||||
},
|
||||
},
|
||||
IsValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/SigNoz/signoz-otel-collector/pkg/parser/grok"
|
||||
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstransformprocessor"
|
||||
"github.com/pkg/errors"
|
||||
"go.opentelemetry.io/collector/pdata/pcommon"
|
||||
@ -22,11 +22,11 @@ func SimulatePipelinesProcessing(
|
||||
pipelines []Pipeline,
|
||||
logs []model.SignozLog,
|
||||
) (
|
||||
[]model.SignozLog, *model.ApiError,
|
||||
output []model.SignozLog, collectorErrorLogs []string, apiErr *model.ApiError,
|
||||
) {
|
||||
|
||||
if len(pipelines) < 1 {
|
||||
return logs, nil
|
||||
return logs, nil, nil
|
||||
}
|
||||
|
||||
// Collector simulation does not guarantee that logs will come
|
||||
@ -45,7 +45,7 @@ func SimulatePipelinesProcessing(
|
||||
// Simulate processing of logs through an otel collector
|
||||
processorConfigs, err := collectorProcessorsForPipelines(pipelines)
|
||||
if err != nil {
|
||||
return nil, model.BadRequest(errors.Wrap(
|
||||
return nil, nil, model.BadRequest(errors.Wrap(
|
||||
err, "could not prepare otel processors for pipelines",
|
||||
))
|
||||
}
|
||||
@ -54,7 +54,7 @@ func SimulatePipelinesProcessing(
|
||||
logstransformprocessor.NewFactory(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, model.InternalError(errors.Wrap(
|
||||
return nil, nil, model.InternalError(errors.Wrap(
|
||||
err, "could not construct processor factory map",
|
||||
))
|
||||
}
|
||||
@ -74,10 +74,9 @@ func SimulatePipelinesProcessing(
|
||||
simulatorInputPLogs,
|
||||
timeout,
|
||||
)
|
||||
collectorErrsText := strings.Join(collectorErrs, "\n")
|
||||
if apiErr != nil {
|
||||
return nil, model.WrapApiError(apiErr, fmt.Sprintf(
|
||||
"could not simulate log pipelines processing.\nCollector errors: %s\n", collectorErrsText,
|
||||
return nil, collectorErrs, model.WrapApiError(apiErr, fmt.Sprintf(
|
||||
"could not simulate log pipelines processing.\nCollector errors",
|
||||
))
|
||||
}
|
||||
|
||||
@ -93,7 +92,7 @@ func SimulatePipelinesProcessing(
|
||||
delete(sigLog.Attributes_int64, inputOrderAttribute)
|
||||
}
|
||||
|
||||
return outputSignozLogs, nil
|
||||
return outputSignozLogs, collectorErrs, nil
|
||||
}
|
||||
|
||||
func collectorProcessorsForPipelines(pipelines []Pipeline) (
|
||||
|
@ -2,6 +2,8 @@ package logparsingpipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -10,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
)
|
||||
|
||||
func TestPipelinePreview(t *testing.T) {
|
||||
@ -101,7 +104,7 @@ func TestPipelinePreview(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
result, err := SimulatePipelinesProcessing(
|
||||
result, collectorErrorLogs, err := SimulatePipelinesProcessing(
|
||||
context.Background(),
|
||||
testPipelines,
|
||||
[]model.SignozLog{
|
||||
@ -111,6 +114,7 @@ func TestPipelinePreview(t *testing.T) {
|
||||
)
|
||||
|
||||
require.Nil(err)
|
||||
require.Equal(0, len(collectorErrorLogs))
|
||||
require.Equal(2, len(result))
|
||||
|
||||
// matching log should have been modified as expected.
|
||||
@ -141,6 +145,175 @@ func TestPipelinePreview(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestGrokParsingPreview(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testPipelines := []Pipeline{
|
||||
{
|
||||
OrderId: 1,
|
||||
Name: "pipeline1",
|
||||
Alias: "pipeline1",
|
||||
Enabled: true,
|
||||
Filter: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "method",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
Operator: "=",
|
||||
Value: "GET",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: []PipelineOperator{
|
||||
{
|
||||
OrderId: 1,
|
||||
ID: "grok",
|
||||
Type: "grok_parser",
|
||||
Enabled: true,
|
||||
Name: "test grok parser",
|
||||
OnError: "send",
|
||||
ParseFrom: "body",
|
||||
ParseTo: "attributes",
|
||||
Pattern: "%{TIMESTAMP_ISO8601:timestamp}%{SPACE}%{WORD:log_level}%{SPACE}%{NOTSPACE:location}%{SPACE}%{GREEDYDATA:message}",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testLog := makeTestLogEntry(
|
||||
"2023-10-26T04:38:00.602Z INFO route/server.go:71 HTTP request received",
|
||||
map[string]string{
|
||||
"method": "GET",
|
||||
},
|
||||
)
|
||||
result, collectorErrorLogs, err := SimulatePipelinesProcessing(
|
||||
context.Background(),
|
||||
testPipelines,
|
||||
[]model.SignozLog{
|
||||
testLog,
|
||||
},
|
||||
)
|
||||
|
||||
require.Nil(err)
|
||||
require.Equal(0, len(collectorErrorLogs))
|
||||
require.Equal(1, len(result))
|
||||
processed := result[0]
|
||||
|
||||
require.Equal("INFO", processed.Attributes_string["log_level"])
|
||||
require.Equal("route/server.go:71", processed.Attributes_string["location"])
|
||||
}
|
||||
|
||||
func TestTraceParsingPreview(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testPipelines := []Pipeline{
|
||||
{
|
||||
OrderId: 1,
|
||||
Name: "pipeline1",
|
||||
Alias: "pipeline1",
|
||||
Enabled: true,
|
||||
Filter: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{
|
||||
Key: "method",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
Type: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
Operator: "=",
|
||||
Value: "GET",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: []PipelineOperator{},
|
||||
},
|
||||
}
|
||||
|
||||
// Start with JSON serialized trace parser to validate deserialization too
|
||||
var traceParserOp PipelineOperator
|
||||
err := json.Unmarshal([]byte(`
|
||||
{
|
||||
"orderId": 1,
|
||||
"enabled": true,
|
||||
"type": "trace_parser",
|
||||
"name": "Test trace parser",
|
||||
"id": "test-trace-parser",
|
||||
"trace_id": {
|
||||
"parse_from": "attributes.test_trace_id"
|
||||
},
|
||||
"span_id": {
|
||||
"parse_from": "attributes.test_span_id"
|
||||
},
|
||||
"trace_flags": {
|
||||
"parse_from": "attributes.test_trace_flags"
|
||||
}
|
||||
}
|
||||
`), &traceParserOp)
|
||||
require.Nil(err)
|
||||
testPipelines[0].Config = append(testPipelines[0].Config, traceParserOp)
|
||||
|
||||
testTraceId, err := utils.RandomHex(16)
|
||||
require.Nil(err)
|
||||
|
||||
testSpanId, err := utils.RandomHex(8)
|
||||
require.Nil(err)
|
||||
|
||||
testTraceFlags, err := utils.RandomHex(1)
|
||||
require.Nil(err)
|
||||
|
||||
testLog := model.SignozLog{
|
||||
Timestamp: uint64(time.Now().UnixNano()),
|
||||
Body: "test log",
|
||||
Attributes_string: map[string]string{
|
||||
"method": "GET",
|
||||
"test_trace_id": testTraceId,
|
||||
"test_span_id": testSpanId,
|
||||
"test_trace_flags": testTraceFlags,
|
||||
},
|
||||
SpanID: "",
|
||||
TraceID: "",
|
||||
TraceFlags: 0,
|
||||
}
|
||||
|
||||
result, collectorErrorLogs, err := SimulatePipelinesProcessing(
|
||||
context.Background(),
|
||||
testPipelines,
|
||||
[]model.SignozLog{
|
||||
testLog,
|
||||
},
|
||||
)
|
||||
require.Nil(err)
|
||||
require.Equal(1, len(result))
|
||||
require.Equal(0, len(collectorErrorLogs))
|
||||
processed := result[0]
|
||||
|
||||
require.Equal(testTraceId, processed.TraceID)
|
||||
require.Equal(testSpanId, processed.SpanID)
|
||||
|
||||
expectedTraceFlags, err := strconv.ParseUint(testTraceFlags, 16, 16)
|
||||
require.Nil(err)
|
||||
require.Equal(uint32(expectedTraceFlags), processed.TraceFlags)
|
||||
|
||||
// trace parser should work even if parse_from value is empty
|
||||
testPipelines[0].Config[0].SpanId.ParseFrom = ""
|
||||
result, collectorErrorLogs, err = SimulatePipelinesProcessing(
|
||||
context.Background(),
|
||||
testPipelines,
|
||||
[]model.SignozLog{
|
||||
testLog,
|
||||
},
|
||||
)
|
||||
require.Nil(err)
|
||||
require.Equal(1, len(result))
|
||||
require.Equal(0, len(collectorErrorLogs))
|
||||
require.Equal("", result[0].SpanID)
|
||||
}
|
||||
|
||||
func makeTestLogEntry(
|
||||
body string,
|
||||
attributes map[string]string,
|
||||
|
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