mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 20:19:13 +08:00
commit
cb22aef36f
2
.github/config.yml
vendored
2
.github/config.yml
vendored
@ -17,7 +17,7 @@ newPRWelcomeComment: >
|
|||||||
# Comment to be posted to on pull requests merged by a first time user
|
# Comment to be posted to on pull requests merged by a first time user
|
||||||
firstPRMergeComment: >
|
firstPRMergeComment: >
|
||||||
Congrats on merging your first pull request!
|
Congrats on merging your first pull request!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
We here at SigNoz are proud of you! 🥳
|
We here at SigNoz are proud of you! 🥳
|
||||||
|
2
.github/workflows/e2e-k3s.yaml
vendored
2
.github/workflows/e2e-k3s.yaml
vendored
@ -57,7 +57,7 @@ jobs:
|
|||||||
--set frontend.service.type=LoadBalancer \
|
--set frontend.service.type=LoadBalancer \
|
||||||
--set queryService.image.tag=$DOCKER_TAG \
|
--set queryService.image.tag=$DOCKER_TAG \
|
||||||
--set frontend.image.tag=$DOCKER_TAG
|
--set frontend.image.tag=$DOCKER_TAG
|
||||||
|
|
||||||
# get pods, services and the container images
|
# get pods, services and the container images
|
||||||
kubectl get pods -n platform
|
kubectl get pods -n platform
|
||||||
kubectl get svc -n platform
|
kubectl get svc -n platform
|
||||||
|
1
.github/workflows/pr_verify_linked_issue.yml
vendored
1
.github/workflows/pr_verify_linked_issue.yml
vendored
@ -17,4 +17,3 @@ jobs:
|
|||||||
uses: hattan/verify-linked-issue-action@v1.1.0
|
uses: hattan/verify-linked-issue-action@v1.1.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
1
.github/workflows/sonar.yml
vendored
1
.github/workflows/sonar.yml
vendored
@ -24,4 +24,3 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|
@ -27,12 +27,6 @@ For x86 chip (amd):
|
|||||||
docker-compose -f docker/clickhouse-setup/docker-compose.yaml up -d
|
docker-compose -f docker/clickhouse-setup/docker-compose.yaml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
For Mac with Apple chip (arm):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker-compose -f docker/clickhouse-setup/docker-compose.arm.yaml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Open http://localhost:3301 in your favourite browser. In couple of minutes, you should see
|
Open http://localhost:3301 in your favourite browser. In couple of minutes, you should see
|
||||||
the data generated from hotrod in SigNoz UI.
|
the data generated from hotrod in SigNoz UI.
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ services:
|
|||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.15.0
|
image: signoz/query-service:0.16.0
|
||||||
command: ["-config=/root/config/prometheus.yml"]
|
command: ["-config=/root/config/prometheus.yml"]
|
||||||
# ports:
|
# ports:
|
||||||
# - "6060:6060" # pprof port
|
# - "6060:6060" # pprof port
|
||||||
@ -166,7 +166,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:0.15.0
|
image: signoz/frontend:0.16.0
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
@ -179,7 +179,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:0.66.3
|
image: signoz/signoz-otel-collector:0.66.4
|
||||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||||
user: root # required for reading docker container logs
|
user: root # required for reading docker container logs
|
||||||
volumes:
|
volumes:
|
||||||
@ -188,6 +188,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}},dockerswarm.service.name={{.Service.Name}},dockerswarm.task.name={{.Task.Name}}
|
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}},dockerswarm.service.name={{.Service.Name}},dockerswarm.task.name={{.Task.Name}}
|
||||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
- DOCKER_MULTI_NODE_CLUSTER=false
|
||||||
|
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||||
ports:
|
ports:
|
||||||
# - "1777:1777" # pprof extension
|
# - "1777:1777" # pprof extension
|
||||||
- "4317:4317" # OTLP gRPC receiver
|
- "4317:4317" # OTLP gRPC receiver
|
||||||
@ -207,7 +208,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
otel-collector-metrics:
|
otel-collector-metrics:
|
||||||
image: signoz/signoz-otel-collector:0.66.3
|
image: signoz/signoz-otel-collector:0.66.4
|
||||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||||
volumes:
|
volumes:
|
||||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||||
|
@ -110,6 +110,7 @@ exporters:
|
|||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||||
|
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
|
@ -41,7 +41,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`
|
# 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:
|
otel-collector:
|
||||||
container_name: otel-collector
|
container_name: otel-collector
|
||||||
image: signoz/signoz-otel-collector:0.66.3
|
image: signoz/signoz-otel-collector:0.66.4
|
||||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||||
# user: root # required for reading docker container logs
|
# user: root # required for reading docker container logs
|
||||||
volumes:
|
volumes:
|
||||||
@ -67,7 +67,7 @@ services:
|
|||||||
|
|
||||||
otel-collector-metrics:
|
otel-collector-metrics:
|
||||||
container_name: otel-collector-metrics
|
container_name: otel-collector-metrics
|
||||||
image: signoz/signoz-otel-collector:0.66.3
|
image: signoz/signoz-otel-collector:0.66.4
|
||||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||||
volumes:
|
volumes:
|
||||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||||
|
@ -153,7 +153,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`
|
# 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:
|
query-service:
|
||||||
image: signoz/query-service:${DOCKER_TAG:-0.15.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.16.0}
|
||||||
container_name: query-service
|
container_name: query-service
|
||||||
command: ["-config=/root/config/prometheus.yml"]
|
command: ["-config=/root/config/prometheus.yml"]
|
||||||
# ports:
|
# ports:
|
||||||
@ -181,7 +181,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.15.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.16.0}
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -193,7 +193,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.3}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.4}
|
||||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||||
user: root # required for reading docker container logs
|
user: root # required for reading docker container logs
|
||||||
volumes:
|
volumes:
|
||||||
@ -202,6 +202,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
||||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
- DOCKER_MULTI_NODE_CLUSTER=false
|
||||||
|
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||||
ports:
|
ports:
|
||||||
# - "1777:1777" # pprof extension
|
# - "1777:1777" # pprof extension
|
||||||
- "4317:4317" # OTLP gRPC receiver
|
- "4317:4317" # OTLP gRPC receiver
|
||||||
@ -218,7 +219,7 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
otel-collector-metrics:
|
otel-collector-metrics:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.3}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.4}
|
||||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||||
volumes:
|
volumes:
|
||||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||||
@ -231,15 +232,15 @@ services:
|
|||||||
<<: *clickhouse-depend
|
<<: *clickhouse-depend
|
||||||
|
|
||||||
hotrod:
|
hotrod:
|
||||||
image: jaegertracing/example-hotrod:1.30
|
image: jaegertracing/example-hotrod:1.30
|
||||||
container_name: hotrod
|
container_name: hotrod
|
||||||
logging:
|
logging:
|
||||||
options:
|
options:
|
||||||
max-size: 50m
|
max-size: 50m
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
command: ["all"]
|
command: ["all"]
|
||||||
environment:
|
environment:
|
||||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||||
|
|
||||||
load-hotrod:
|
load-hotrod:
|
||||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||||
|
@ -119,6 +119,7 @@ exporters:
|
|||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||||
|
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
|
@ -81,6 +81,11 @@ check_os() {
|
|||||||
os="centos"
|
os="centos"
|
||||||
package_manager="yum"
|
package_manager="yum"
|
||||||
;;
|
;;
|
||||||
|
Rocky*)
|
||||||
|
desired_os=1
|
||||||
|
os="centos"
|
||||||
|
package_manager="yum"
|
||||||
|
;;
|
||||||
SLES*)
|
SLES*)
|
||||||
desired_os=1
|
desired_os=1
|
||||||
os="sles"
|
os="sles"
|
||||||
|
@ -25,6 +25,7 @@ const config: Config.InitialOptions = {
|
|||||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||||
moduleDirectories: ['node_modules', 'src'],
|
moduleDirectories: ['node_modules', 'src'],
|
||||||
|
testEnvironment: 'jest-environment-jsdom',
|
||||||
testEnvironmentOptions: {
|
testEnvironmentOptions: {
|
||||||
'jest-playwright': {
|
'jest-playwright': {
|
||||||
browsers: ['chromium', 'firefox', 'webkit'],
|
browsers: ['chromium', 'firefox', 'webkit'],
|
||||||
|
@ -31,10 +31,6 @@
|
|||||||
"@ant-design/icons": "4.8.0",
|
"@ant-design/icons": "4.8.0",
|
||||||
"@grafana/data": "^8.4.3",
|
"@grafana/data": "^8.4.3",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "^4.3.1",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
|
||||||
"@testing-library/react": "^11.1.0",
|
|
||||||
"@testing-library/user-event": "^12.1.10",
|
|
||||||
"@welldone-software/why-did-you-render": "^6.2.1",
|
|
||||||
"@xstate/react": "^3.0.0",
|
"@xstate/react": "^3.0.0",
|
||||||
"antd": "5.0.5",
|
"antd": "5.0.5",
|
||||||
"axios": "^0.21.0",
|
"axios": "^0.21.0",
|
||||||
@ -82,7 +78,6 @@
|
|||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-use": "^17.3.2",
|
"react-use": "^17.3.2",
|
||||||
"react-virtuoso": "4.0.3",
|
"react-virtuoso": "4.0.3",
|
||||||
"react-vis": "^1.11.7",
|
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"stream": "^0.0.2",
|
"stream": "^0.0.2",
|
||||||
@ -122,7 +117,9 @@
|
|||||||
"@commitlint/config-conventional": "^16.2.4",
|
"@commitlint/config-conventional": "^16.2.4",
|
||||||
"@jest/globals": "^27.5.1",
|
"@jest/globals": "^27.5.1",
|
||||||
"@playwright/test": "^1.22.0",
|
"@playwright/test": "^1.22.0",
|
||||||
"@testing-library/react-hooks": "^7.0.2",
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
|
"@testing-library/react": "13.4.0",
|
||||||
|
"@testing-library/user-event": "14.4.3",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
"@types/compression-webpack-plugin": "^9.0.0",
|
"@types/compression-webpack-plugin": "^9.0.0",
|
||||||
"@types/copy-webpack-plugin": "^8.0.1",
|
"@types/copy-webpack-plugin": "^8.0.1",
|
||||||
@ -138,6 +135,7 @@
|
|||||||
"@types/react-dom": "18.0.10",
|
"@types/react-dom": "18.0.10",
|
||||||
"@types/react-grid-layout": "^1.1.2",
|
"@types/react-grid-layout": "^1.1.2",
|
||||||
"@types/react-redux": "^7.1.11",
|
"@types/react-redux": "^7.1.11",
|
||||||
|
"@types/react-resizable": "3.0.3",
|
||||||
"@types/react-router-dom": "^5.1.6",
|
"@types/react-router-dom": "^5.1.6",
|
||||||
"@types/redux": "^3.6.0",
|
"@types/redux": "^3.6.0",
|
||||||
"@types/styled-components": "^5.1.4",
|
"@types/styled-components": "^5.1.4",
|
||||||
@ -147,6 +145,7 @@
|
|||||||
"@types/webpack-dev-server": "^4.3.0",
|
"@types/webpack-dev-server": "^4.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||||
"@typescript-eslint/parser": "^4.28.2",
|
"@typescript-eslint/parser": "^4.28.2",
|
||||||
|
"@welldone-software/why-did-you-render": "6.2.1",
|
||||||
"autoprefixer": "^9.0.0",
|
"autoprefixer": "^9.0.0",
|
||||||
"babel-plugin-styled-components": "^1.12.0",
|
"babel-plugin-styled-components": "^1.12.0",
|
||||||
"compression-webpack-plugin": "9.0.0",
|
"compression-webpack-plugin": "9.0.0",
|
||||||
@ -176,6 +175,7 @@
|
|||||||
"portfinder-sync": "^0.0.2",
|
"portfinder-sync": "^0.0.2",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
"react-hot-loader": "^4.13.0",
|
"react-hot-loader": "^4.13.0",
|
||||||
|
"react-resizable": "3.0.4",
|
||||||
"ts-jest": "^27.1.4",
|
"ts-jest": "^27.1.4",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"typescript-plugin-css-modules": "^3.4.0",
|
"typescript-plugin-css-modules": "^3.4.0",
|
||||||
|
@ -47,6 +47,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const currentRoute = mapRoutes.get('current');
|
const currentRoute = mapRoutes.get('current');
|
||||||
|
|
||||||
const navigateToLoginIfNotLoggedIn = (isLoggedIn = isLoggedInState): void => {
|
const navigateToLoginIfNotLoggedIn = (isLoggedIn = isLoggedInState): void => {
|
||||||
@ -106,7 +108,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
} else {
|
} else {
|
||||||
Logout();
|
Logout();
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: response.error || t('something_went_wrong'),
|
message: response.error || t('something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -155,7 +157,12 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
|
|
||||||
// NOTE: disabling this rule as there is no need to have div
|
// NOTE: disabling this rule as there is no need to have div
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
return <>{children}</>;
|
return (
|
||||||
|
<>
|
||||||
|
{NotificationElement}
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PrivateRouteProps {
|
interface PrivateRouteProps {
|
||||||
|
@ -37,7 +37,7 @@ const getSpans = async (
|
|||||||
start: String(props.start),
|
start: String(props.start),
|
||||||
end: String(props.end),
|
end: String(props.end),
|
||||||
function: props.function,
|
function: props.function,
|
||||||
groupBy: props.groupBy,
|
groupBy: props.groupBy === 'none' ? '' : props.groupBy,
|
||||||
step: props.step,
|
step: props.step,
|
||||||
tags: updatedSelectedTags,
|
tags: updatedSelectedTags,
|
||||||
...nonDuration,
|
...nonDuration,
|
||||||
|
@ -23,8 +23,10 @@ import {
|
|||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
|
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
|
import React, { memo, useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { hasData } from './hasData';
|
import { hasData } from './hasData';
|
||||||
import { getAxisLabelColor } from './helpers';
|
import { getAxisLabelColor } from './helpers';
|
||||||
@ -80,6 +82,7 @@ function Graph({
|
|||||||
onDragSelect,
|
onDragSelect,
|
||||||
dragSelectColor,
|
dragSelectColor,
|
||||||
}: GraphProps): JSX.Element {
|
}: GraphProps): JSX.Element {
|
||||||
|
const nearestDatasetIndex = useRef<null | number>(null);
|
||||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
@ -150,6 +153,10 @@ function Graph({
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
title(context) {
|
||||||
|
const date = dayjs(context[0].parsed.x);
|
||||||
|
return date.format('MMM DD, YYYY, HH:mm:ss');
|
||||||
|
},
|
||||||
label(context) {
|
label(context) {
|
||||||
let label = context.dataset.label || '';
|
let label = context.dataset.label || '';
|
||||||
|
|
||||||
@ -159,8 +166,16 @@ function Graph({
|
|||||||
if (context.parsed.y !== null) {
|
if (context.parsed.y !== null) {
|
||||||
label += getToolTipValue(context.parsed.y.toString(), yAxisUnit);
|
label += getToolTipValue(context.parsed.y.toString(), yAxisUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
return label;
|
return label;
|
||||||
},
|
},
|
||||||
|
labelTextColor(labelData) {
|
||||||
|
if (labelData.datasetIndex === nearestDatasetIndex.current) {
|
||||||
|
return 'rgba(255, 255, 255, 1)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'rgba(255, 255, 255, 0.75)';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[dragSelectPluginId]: createDragSelectPluginOptions(
|
[dragSelectPluginId]: createDragSelectPluginOptions(
|
||||||
@ -226,12 +241,38 @@ function Graph({
|
|||||||
tension: 0,
|
tension: 0,
|
||||||
cubicInterpolationMode: 'monotone',
|
cubicInterpolationMode: 'monotone',
|
||||||
},
|
},
|
||||||
|
point: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
hoverBackgroundColor: (ctx: any) => {
|
||||||
|
if (ctx?.element?.options?.borderColor) {
|
||||||
|
return ctx.element.options.borderColor;
|
||||||
|
}
|
||||||
|
return 'rgba(0,0,0,0.1)';
|
||||||
|
},
|
||||||
|
hoverRadius: 5,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
onClick: (event, element, chart) => {
|
onClick: (event, element, chart) => {
|
||||||
if (onClickHandler) {
|
if (onClickHandler) {
|
||||||
onClickHandler(event, element, chart, data);
|
onClickHandler(event, element, chart, data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onHover: (event, _, chart) => {
|
||||||
|
if (event.native) {
|
||||||
|
const interactions = chart.getElementsAtEventForMode(
|
||||||
|
event.native,
|
||||||
|
'nearest',
|
||||||
|
{
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (interactions[0]) {
|
||||||
|
nearestDatasetIndex.current = interactions[0].datasetIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartHasData = hasData(data);
|
const chartHasData = hasData(data);
|
||||||
@ -334,4 +375,7 @@ Graph.defaultProps = {
|
|||||||
onDragSelect: undefined,
|
onDragSelect: undefined,
|
||||||
dragSelectColor: undefined,
|
dragSelectColor: undefined,
|
||||||
};
|
};
|
||||||
export default Graph;
|
|
||||||
|
export default memo(Graph, (prevProps, nextProps) =>
|
||||||
|
isEqual(prevProps.data, nextProps.data),
|
||||||
|
);
|
||||||
|
@ -2,7 +2,9 @@ import { Button, Popover } from 'antd';
|
|||||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||||
import React, { memo, useCallback, useMemo } from 'react';
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
|
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
@ -14,7 +16,7 @@ function AddToQueryHOC({
|
|||||||
const {
|
const {
|
||||||
searchFilter: { queryString },
|
searchFilter: { queryString },
|
||||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
const generatedQuery = useMemo(
|
const generatedQuery = useMemo(
|
||||||
() => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }),
|
() => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }),
|
||||||
@ -31,7 +33,9 @@ function AddToQueryHOC({
|
|||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_SEARCH_QUERY_STRING,
|
type: SET_SEARCH_QUERY_STRING,
|
||||||
payload: updatedQueryString,
|
payload: {
|
||||||
|
searchQueryString: updatedQueryString,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}, [dispatch, generatedQuery, queryString]);
|
}, [dispatch, generatedQuery, queryString]);
|
||||||
|
|
||||||
|
@ -7,14 +7,14 @@ function CopyClipboardHOC({
|
|||||||
children,
|
children,
|
||||||
}: CopyClipboardHOCProps): JSX.Element {
|
}: CopyClipboardHOCProps): JSX.Element {
|
||||||
const [value, setCopy] = useCopyToClipboard();
|
const [value, setCopy] = useCopyToClipboard();
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value.value) {
|
if (value.value) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: 'Copied to clipboard',
|
message: 'Copied to clipboard',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [value, notifications]);
|
||||||
|
|
||||||
const onClick = useCallback((): void => {
|
const onClick = useCallback((): void => {
|
||||||
setCopy(textToCopy);
|
setCopy(textToCopy);
|
||||||
@ -22,6 +22,7 @@ function CopyClipboardHOC({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span onClick={onClick} onKeyDown={onClick} role="button" tabIndex={0}>
|
<span onClick={onClick} onKeyDown={onClick} role="button" tabIndex={0}>
|
||||||
|
{NotificationElement}
|
||||||
<Popover
|
<Popover
|
||||||
placement="top"
|
placement="top"
|
||||||
content={<span style={{ fontSize: '0.9rem' }}>Copy to clipboard</span>}
|
content={<span style={{ fontSize: '0.9rem' }}>Copy to clipboard</span>}
|
||||||
|
@ -79,6 +79,7 @@ function LogItem({ logData }: LogItemProps): JSX.Element {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||||
const [, setCopy] = useCopyToClipboard();
|
const [, setCopy] = useCopyToClipboard();
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const handleDetailedView = useCallback(() => {
|
const handleDetailedView = useCallback(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -89,27 +90,22 @@ function LogItem({ logData }: LogItemProps): JSX.Element {
|
|||||||
|
|
||||||
const handleCopyJSON = (): void => {
|
const handleCopyJSON = (): void => {
|
||||||
setCopy(JSON.stringify(logData, null, 2));
|
setCopy(JSON.stringify(logData, null, 2));
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: 'Copied to clipboard',
|
message: 'Copied to clipboard',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
{NotificationElement}
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
{'{'}
|
{'{'}
|
||||||
<LogContainer>
|
<LogContainer>
|
||||||
<>
|
<>
|
||||||
<LogGeneralField
|
<LogGeneralField fieldKey="log" fieldValue={flattenLogData.body} />
|
||||||
fieldKey="log"
|
|
||||||
fieldValue={flattenLogData.body as never}
|
|
||||||
/>
|
|
||||||
{flattenLogData.stream && (
|
{flattenLogData.stream && (
|
||||||
<LogGeneralField
|
<LogGeneralField fieldKey="stream" fieldValue={flattenLogData.stream} />
|
||||||
fieldKey="stream"
|
|
||||||
fieldValue={flattenLogData.stream as never}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<LogGeneralField
|
<LogGeneralField
|
||||||
fieldKey="timestamp"
|
fieldKey="timestamp"
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
51
frontend/src/components/ResizeTable/ResizableHeader.tsx
Normal file
51
frontend/src/components/ResizeTable/ResizableHeader.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Resizable, ResizeCallbackData } from 'react-resizable';
|
||||||
|
|
||||||
|
import { enableUserSelectHack } from './config';
|
||||||
|
import { SpanStyle } from './styles';
|
||||||
|
|
||||||
|
function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
|
||||||
|
const { onResize, width, ...restProps } = props;
|
||||||
|
|
||||||
|
const handle = useMemo(
|
||||||
|
() => (
|
||||||
|
<SpanStyle
|
||||||
|
className="react-resizable-handle"
|
||||||
|
onClick={(e): void => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const draggableOpts = useMemo(
|
||||||
|
() => ({
|
||||||
|
enableUserSelectHack,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!width) {
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return <th {...restProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Resizable
|
||||||
|
width={width}
|
||||||
|
height={0}
|
||||||
|
handle={handle}
|
||||||
|
onResize={onResize}
|
||||||
|
draggableOpts={draggableOpts}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
|
<th {...restProps} />
|
||||||
|
</Resizable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResizableHeaderProps {
|
||||||
|
onResize: (e: React.SyntheticEvent<Element>, data: ResizeCallbackData) => void;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResizableHeader;
|
51
frontend/src/components/ResizeTable/ResizeTable.tsx
Normal file
51
frontend/src/components/ResizeTable/ResizeTable.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { Table } from 'antd';
|
||||||
|
import type { TableProps } from 'antd/es/table';
|
||||||
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { ResizeCallbackData } from 'react-resizable';
|
||||||
|
|
||||||
|
import ResizableHeader from './ResizableHeader';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function ResizeTable({ columns, ...restprops }: TableProps<any>): JSX.Element {
|
||||||
|
const [columnsData, setColumns] = useState<ColumnsType>(columns || []);
|
||||||
|
|
||||||
|
const handleResize = useCallback(
|
||||||
|
(index: number) => (
|
||||||
|
_e: React.SyntheticEvent<Element>,
|
||||||
|
{ size }: ResizeCallbackData,
|
||||||
|
): void => {
|
||||||
|
const newColumns = [...columnsData];
|
||||||
|
newColumns[index] = {
|
||||||
|
...newColumns[index],
|
||||||
|
width: size.width,
|
||||||
|
};
|
||||||
|
setColumns(newColumns);
|
||||||
|
},
|
||||||
|
[columnsData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const mergeColumns = useMemo(
|
||||||
|
() =>
|
||||||
|
columnsData.map((col, index) => ({
|
||||||
|
...col,
|
||||||
|
onHeaderCell: (column: ColumnsType<unknown>[number]): unknown => ({
|
||||||
|
width: column.width,
|
||||||
|
onResize: handleResize(index),
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
[columnsData, handleResize],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...restprops}
|
||||||
|
components={{ header: { cell: ResizableHeader } }}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
columns={mergeColumns as ColumnsType<any>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResizeTable;
|
1
frontend/src/components/ResizeTable/config.ts
Normal file
1
frontend/src/components/ResizeTable/config.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const enableUserSelectHack = { enableUserSelectHack: false };
|
4
frontend/src/components/ResizeTable/index.ts
Normal file
4
frontend/src/components/ResizeTable/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import ResizableHeader from './ResizableHeader';
|
||||||
|
import ResizeTable from './ResizeTable';
|
||||||
|
|
||||||
|
export { ResizableHeader, ResizeTable };
|
11
frontend/src/components/ResizeTable/styles.ts
Normal file
11
frontend/src/components/ResizeTable/styles.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const SpanStyle = styled.span`
|
||||||
|
position: absolute;
|
||||||
|
right: -5px;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
width: 10px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: col-resize;
|
||||||
|
`;
|
@ -37,6 +37,7 @@ const themeColors = {
|
|||||||
matterhornGrey: '#555555',
|
matterhornGrey: '#555555',
|
||||||
whiteCream: '#ffffffd5',
|
whiteCream: '#ffffffd5',
|
||||||
black: '#000000',
|
black: '#000000',
|
||||||
|
lightgrey: '#ddd',
|
||||||
};
|
};
|
||||||
|
|
||||||
export { themeColors };
|
export { themeColors };
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { Button, notification, Table } from 'antd';
|
import { Button, notification } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -34,11 +35,13 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
|||||||
title: t('column_channel_name'),
|
title: t('column_channel_name'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('column_channel_type'),
|
title: t('column_channel_type'),
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
|
width: 80,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -48,6 +51,7 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
|||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
width: 80,
|
||||||
render: (id: string): JSX.Element => (
|
render: (id: string): JSX.Element => (
|
||||||
<>
|
<>
|
||||||
<Button onClick={(): void => onClickEditHandler(id)} type="link">
|
<Button onClick={(): void => onClickEditHandler(id)} type="link">
|
||||||
@ -62,8 +66,7 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Element}
|
{Element}
|
||||||
|
<ResizeTable columns={columns} dataSource={channels} rowKey="id" />
|
||||||
<Table rowKey="id" dataSource={channels} columns={columns} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
notification,
|
notification,
|
||||||
Space,
|
Space,
|
||||||
Table,
|
|
||||||
TableProps,
|
TableProps,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
@ -16,6 +15,7 @@ import { ColumnsType } from 'antd/lib/table';
|
|||||||
import { FilterConfirmProps } from 'antd/lib/table/interface';
|
import { FilterConfirmProps } from 'antd/lib/table/interface';
|
||||||
import getAll from 'api/errors/getAll';
|
import getAll from 'api/errors/getAll';
|
||||||
import getErrorCounts from 'api/errors/getErrorCounts';
|
import getErrorCounts from 'api/errors/getErrorCounts';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
@ -127,14 +127,15 @@ function AllErrors(): JSX.Element {
|
|||||||
enabled: !loading,
|
enabled: !loading,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.error) {
|
if (data?.error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: data.error || t('something_went_wrong'),
|
message: data.error || t('something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data?.error, data?.payload, t]);
|
}, [data?.error, data?.payload, t, notifications]);
|
||||||
|
|
||||||
const getDateValue = (value: string): JSX.Element => (
|
const getDateValue = (value: string): JSX.Element => (
|
||||||
<Typography>{dayjs(value).format('DD/MM/YYYY HH:mm:ss A')}</Typography>
|
<Typography>{dayjs(value).format('DD/MM/YYYY HH:mm:ss A')}</Typography>
|
||||||
@ -258,6 +259,7 @@ function AllErrors(): JSX.Element {
|
|||||||
const columns: ColumnsType<Exception> = [
|
const columns: ColumnsType<Exception> = [
|
||||||
{
|
{
|
||||||
title: 'Exception Type',
|
title: 'Exception Type',
|
||||||
|
width: 100,
|
||||||
dataIndex: 'exceptionType',
|
dataIndex: 'exceptionType',
|
||||||
key: 'exceptionType',
|
key: 'exceptionType',
|
||||||
...getFilter(onExceptionTypeFilter, 'Search By Exception', 'exceptionType'),
|
...getFilter(onExceptionTypeFilter, 'Search By Exception', 'exceptionType'),
|
||||||
@ -283,6 +285,7 @@ function AllErrors(): JSX.Element {
|
|||||||
title: 'Error Message',
|
title: 'Error Message',
|
||||||
dataIndex: 'exceptionMessage',
|
dataIndex: 'exceptionMessage',
|
||||||
key: 'exceptionMessage',
|
key: 'exceptionMessage',
|
||||||
|
width: 100,
|
||||||
render: (value): JSX.Element => (
|
render: (value): JSX.Element => (
|
||||||
<Tooltip overlay={(): JSX.Element => value}>
|
<Tooltip overlay={(): JSX.Element => value}>
|
||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
@ -297,6 +300,7 @@ function AllErrors(): JSX.Element {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Count',
|
title: 'Count',
|
||||||
|
width: 50,
|
||||||
dataIndex: 'exceptionCount',
|
dataIndex: 'exceptionCount',
|
||||||
key: 'exceptionCount',
|
key: 'exceptionCount',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
@ -309,6 +313,7 @@ function AllErrors(): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Last Seen',
|
title: 'Last Seen',
|
||||||
dataIndex: 'lastSeen',
|
dataIndex: 'lastSeen',
|
||||||
|
width: 80,
|
||||||
key: 'lastSeen',
|
key: 'lastSeen',
|
||||||
render: getDateValue,
|
render: getDateValue,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
@ -321,6 +326,7 @@ function AllErrors(): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'First Seen',
|
title: 'First Seen',
|
||||||
dataIndex: 'firstSeen',
|
dataIndex: 'firstSeen',
|
||||||
|
width: 80,
|
||||||
key: 'firstSeen',
|
key: 'firstSeen',
|
||||||
render: getDateValue,
|
render: getDateValue,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
@ -333,6 +339,7 @@ function AllErrors(): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Application',
|
title: 'Application',
|
||||||
dataIndex: 'serviceName',
|
dataIndex: 'serviceName',
|
||||||
|
width: 100,
|
||||||
key: 'serviceName',
|
key: 'serviceName',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
defaultSortOrder: getDefaultOrder(
|
defaultSortOrder: getDefaultOrder(
|
||||||
@ -379,21 +386,24 @@ function AllErrors(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<>
|
||||||
tableLayout="fixed"
|
{NotificationElement}
|
||||||
dataSource={data?.payload as Exception[]}
|
<ResizeTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="firstSeen"
|
tableLayout="fixed"
|
||||||
loading={isLoading || false || errorCountResponse.status === 'loading'}
|
dataSource={data?.payload as Exception[]}
|
||||||
pagination={{
|
rowKey="firstSeen"
|
||||||
pageSize: getUpdatedPageSize,
|
loading={isLoading || false || errorCountResponse.status === 'loading'}
|
||||||
responsive: true,
|
pagination={{
|
||||||
current: getUpdatedOffset / 10 + 1,
|
pageSize: getUpdatedPageSize,
|
||||||
position: ['bottomLeft'],
|
responsive: true,
|
||||||
total: errorCountResponse.data?.payload || 0,
|
current: getUpdatedOffset / 10 + 1,
|
||||||
}}
|
position: ['bottomLeft'],
|
||||||
onChange={onChangeHandler}
|
total: errorCountResponse.data?.payload || 0,
|
||||||
/>
|
}}
|
||||||
|
onChange={onChangeHandler}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
const latestVersionCounter = useRef(0);
|
const latestVersionCounter = useRef(0);
|
||||||
const latestConfigCounter = useRef(0);
|
const latestConfigCounter = useRef(0);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
getUserLatestVersionResponse.isFetched &&
|
getUserLatestVersionResponse.isFetched &&
|
||||||
@ -105,7 +107,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
isError: true,
|
isError: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('oops_something_went_wrong_version'),
|
message: t('oops_something_went_wrong_version'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -123,7 +125,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
isError: true,
|
isError: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('oops_something_went_wrong_version'),
|
message: t('oops_something_went_wrong_version'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -219,12 +221,14 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
getDynamicConfigsResponse.data,
|
getDynamicConfigsResponse.data,
|
||||||
getDynamicConfigsResponse.isFetched,
|
getDynamicConfigsResponse.isFetched,
|
||||||
getDynamicConfigsResponse.isSuccess,
|
getDynamicConfigsResponse.isSuccess,
|
||||||
|
notifications,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isToDisplayLayout = isLoggedIn;
|
const isToDisplayLayout = isLoggedIn;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
{NotificationElement}
|
||||||
{isToDisplayLayout && <Header />}
|
{isToDisplayLayout && <Header />}
|
||||||
<Layout>
|
<Layout>
|
||||||
{isToDisplayLayout && <SideNav />}
|
{isToDisplayLayout && <SideNav />}
|
||||||
|
@ -6,6 +6,16 @@ import {
|
|||||||
defaultMatchType,
|
defaultMatchType,
|
||||||
} from 'types/api/alerts/def';
|
} from 'types/api/alerts/def';
|
||||||
|
|
||||||
|
const defaultAlertDescription =
|
||||||
|
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})';
|
||||||
|
const defaultAlertSummary =
|
||||||
|
'The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}';
|
||||||
|
|
||||||
|
const defaultAnnotations = {
|
||||||
|
description: defaultAlertDescription,
|
||||||
|
summary: defaultAlertSummary,
|
||||||
|
};
|
||||||
|
|
||||||
export const alertDefaults: AlertDef = {
|
export const alertDefaults: AlertDef = {
|
||||||
alertType: AlertTypes.METRICS_BASED_ALERT,
|
alertType: AlertTypes.METRICS_BASED_ALERT,
|
||||||
condition: {
|
condition: {
|
||||||
@ -38,9 +48,7 @@ export const alertDefaults: AlertDef = {
|
|||||||
labels: {
|
labels: {
|
||||||
severity: 'warning',
|
severity: 'warning',
|
||||||
},
|
},
|
||||||
annotations: {
|
annotations: defaultAnnotations,
|
||||||
description: 'A new alert',
|
|
||||||
},
|
|
||||||
evalWindow: defaultEvalWindow,
|
evalWindow: defaultEvalWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,9 +93,7 @@ export const logAlertDefaults: AlertDef = {
|
|||||||
severity: 'warning',
|
severity: 'warning',
|
||||||
details: `${window.location.protocol}//${window.location.host}/logs`,
|
details: `${window.location.protocol}//${window.location.host}/logs`,
|
||||||
},
|
},
|
||||||
annotations: {
|
annotations: defaultAnnotations,
|
||||||
description: 'A new log-based alert',
|
|
||||||
},
|
|
||||||
evalWindow: defaultEvalWindow,
|
evalWindow: defaultEvalWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,9 +138,7 @@ export const traceAlertDefaults: AlertDef = {
|
|||||||
severity: 'warning',
|
severity: 'warning',
|
||||||
details: `${window.location.protocol}//${window.location.host}/traces`,
|
details: `${window.location.protocol}//${window.location.host}/traces`,
|
||||||
},
|
},
|
||||||
annotations: {
|
annotations: defaultAnnotations,
|
||||||
description: 'A new trace-based alert',
|
|
||||||
},
|
|
||||||
evalWindow: defaultEvalWindow,
|
evalWindow: defaultEvalWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,8 +183,6 @@ export const exceptionAlertDefaults: AlertDef = {
|
|||||||
severity: 'warning',
|
severity: 'warning',
|
||||||
details: `${window.location.protocol}//${window.location.host}/exceptions`,
|
details: `${window.location.protocol}//${window.location.host}/exceptions`,
|
||||||
},
|
},
|
||||||
annotations: {
|
annotations: defaultAnnotations,
|
||||||
description: 'A new exceptions-based alert',
|
|
||||||
},
|
|
||||||
evalWindow: defaultEvalWindow,
|
evalWindow: defaultEvalWindow,
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Button, Divider, notification, Space, Table, Typography } from 'antd';
|
import { Button, Divider, notification, Space, Typography } from 'antd';
|
||||||
import getNextPrevId from 'api/errors/getNextPrevId';
|
import getNextPrevId from 'api/errors/getNextPrevId';
|
||||||
import Editor from 'components/Editor';
|
import Editor from 'components/Editor';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { getNanoSeconds } from 'container/AllError/utils';
|
import { getNanoSeconds } from 'container/AllError/utils';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -53,12 +54,14 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
title: 'Key',
|
title: 'Key',
|
||||||
|
width: 100,
|
||||||
dataIndex: 'key',
|
dataIndex: 'key',
|
||||||
key: 'key',
|
key: 'key',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Value',
|
title: 'Value',
|
||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
|
width: 100,
|
||||||
key: 'value',
|
key: 'value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -77,13 +80,15 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onClickErrorIdHandler = async (
|
const onClickErrorIdHandler = async (
|
||||||
id: string,
|
id: string,
|
||||||
timestamp: string,
|
timestamp: string,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
if (id.length === 0) {
|
if (id.length === 0) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error Id cannot be empty',
|
message: 'Error Id cannot be empty',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -95,7 +100,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
|||||||
}×tamp=${getNanoSeconds(timestamp)}&errorId=${id}`,
|
}×tamp=${getNanoSeconds(timestamp)}&errorId=${id}`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong'),
|
message: t('something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -116,6 +121,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
<Typography>{errorDetail.exceptionType}</Typography>
|
<Typography>{errorDetail.exceptionType}</Typography>
|
||||||
<Typography>{errorDetail.exceptionMessage}</Typography>
|
<Typography>{errorDetail.exceptionMessage}</Typography>
|
||||||
<Divider />
|
<Divider />
|
||||||
@ -167,7 +173,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
|||||||
|
|
||||||
<EditorContainer>
|
<EditorContainer>
|
||||||
<Space direction="vertical">
|
<Space direction="vertical">
|
||||||
<Table tableLayout="fixed" columns={columns} dataSource={data} />
|
<ResizeTable columns={columns} tableLayout="fixed" dataSource={data} />
|
||||||
</Space>
|
</Space>
|
||||||
</EditorContainer>
|
</EditorContainer>
|
||||||
</>
|
</>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Input, Select } from 'antd';
|
import { Form, Input, Select } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import { LabelFilterStatement } from 'container/CreateAlertChannels/config';
|
import { LabelFilterStatement } from 'container/CreateAlertChannels/config';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@ -10,7 +9,7 @@ const { Option } = Select;
|
|||||||
// point
|
// point
|
||||||
function LabelFilterForm({ setFilter }: LabelFilterProps): JSX.Element {
|
function LabelFilterForm({ setFilter }: LabelFilterProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<FormItem name="label_filter" label="Notify When (Optional)">
|
<Form.Item name="label_filter" label="Notify When (Optional)">
|
||||||
<Input.Group compact>
|
<Input.Group compact>
|
||||||
<Select
|
<Select
|
||||||
defaultValue="Severity"
|
defaultValue="Severity"
|
||||||
@ -51,7 +50,7 @@ function LabelFilterForm({ setFilter }: LabelFilterProps): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -11,7 +10,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
const { t } = useTranslation('channels');
|
const { t } = useTranslation('channels');
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormItem name="routing_key" label={t('field_pager_routing_key')} required>
|
<Form.Item name="routing_key" label={t('field_pager_routing_key')} required>
|
||||||
<Input
|
<Input
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
setSelectedConfig((value) => ({
|
setSelectedConfig((value) => ({
|
||||||
@ -20,9 +19,9 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="description"
|
name="description"
|
||||||
help={t('help_pager_description')}
|
help={t('help_pager_description')}
|
||||||
label={t('field_pager_description')}
|
label={t('field_pager_description')}
|
||||||
@ -38,9 +37,9 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
placeholder={t('placeholder_pager_description')}
|
placeholder={t('placeholder_pager_description')}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="severity"
|
name="severity"
|
||||||
help={t('help_pager_severity')}
|
help={t('help_pager_severity')}
|
||||||
label={t('field_pager_severity')}
|
label={t('field_pager_severity')}
|
||||||
@ -53,9 +52,9 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="details"
|
name="details"
|
||||||
help={t('help_pager_details')}
|
help={t('help_pager_details')}
|
||||||
label={t('field_pager_details')}
|
label={t('field_pager_details')}
|
||||||
@ -69,9 +68,9 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="component"
|
name="component"
|
||||||
help={t('help_pager_component')}
|
help={t('help_pager_component')}
|
||||||
label={t('field_pager_component')}
|
label={t('field_pager_component')}
|
||||||
@ -84,9 +83,9 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="group"
|
name="group"
|
||||||
help={t('help_pager_group')}
|
help={t('help_pager_group')}
|
||||||
label={t('field_pager_group')}
|
label={t('field_pager_group')}
|
||||||
@ -99,9 +98,9 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="class"
|
name="class"
|
||||||
help={t('help_pager_class')}
|
help={t('help_pager_class')}
|
||||||
label={t('field_pager_class')}
|
label={t('field_pager_class')}
|
||||||
@ -114,8 +113,8 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="client"
|
name="client"
|
||||||
help={t('help_pager_client')}
|
help={t('help_pager_client')}
|
||||||
label={t('field_pager_client')}
|
label={t('field_pager_client')}
|
||||||
@ -128,9 +127,9 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="client_url"
|
name="client_url"
|
||||||
help={t('help_pager_client_url')}
|
help={t('help_pager_client_url')}
|
||||||
label={t('field_pager_client_url')}
|
label={t('field_pager_client_url')}
|
||||||
@ -143,7 +142,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -12,7 +11,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormItem name="api_url" label={t('field_webhook_url')}>
|
<Form.Item name="api_url" label={t('field_webhook_url')}>
|
||||||
<Input
|
<Input
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
setSelectedConfig((value) => ({
|
setSelectedConfig((value) => ({
|
||||||
@ -21,9 +20,9 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="channel"
|
name="channel"
|
||||||
help={t('slack_channel_help')}
|
help={t('slack_channel_help')}
|
||||||
label={t('field_slack_recipient')}
|
label={t('field_slack_recipient')}
|
||||||
@ -36,9 +35,9 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem name="title" label={t('field_slack_title')}>
|
<Form.Item name="title" label={t('field_slack_title')}>
|
||||||
<TextArea
|
<TextArea
|
||||||
rows={4}
|
rows={4}
|
||||||
// value={`[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n{{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n{{\" \"}}(\n{{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}=\"{{ $label.Value -}}\"\n {{- end }}\n{{- end -}}\n)\n{{- end }}`}
|
// value={`[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n{{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n{{\" \"}}(\n{{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}=\"{{ $label.Value -}}\"\n {{- end }}\n{{- end -}}\n)\n{{- end }}`}
|
||||||
@ -49,9 +48,9 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem name="text" label={t('field_slack_description')}>
|
<Form.Item name="text" label={t('field_slack_description')}>
|
||||||
<TextArea
|
<TextArea
|
||||||
onChange={(event): void =>
|
onChange={(event): void =>
|
||||||
setSelectedConfig((value) => ({
|
setSelectedConfig((value) => ({
|
||||||
@ -61,7 +60,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
placeholder={t('placeholder_slack_description')}
|
placeholder={t('placeholder_slack_description')}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -10,7 +9,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormItem name="api_url" label={t('field_webhook_url')}>
|
<Form.Item name="api_url" label={t('field_webhook_url')}>
|
||||||
<Input
|
<Input
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
setSelectedConfig((value) => ({
|
setSelectedConfig((value) => ({
|
||||||
@ -19,8 +18,8 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="username"
|
name="username"
|
||||||
label={t('field_webhook_username')}
|
label={t('field_webhook_username')}
|
||||||
help={t('help_webhook_username')}
|
help={t('help_webhook_username')}
|
||||||
@ -33,8 +32,8 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
<FormItem
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
label="Password (optional)"
|
label="Password (optional)"
|
||||||
help={t('help_webhook_password')}
|
help={t('help_webhook_password')}
|
||||||
@ -48,7 +47,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import { Store } from 'antd/lib/form/interface';
|
import { Store } from 'antd/lib/form/interface';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import {
|
import {
|
||||||
@ -59,7 +58,7 @@ function FormAlertChannels({
|
|||||||
<Title level={3}>{title}</Title>
|
<Title level={3}>{title}</Title>
|
||||||
|
|
||||||
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
||||||
<FormItem label={t('field_channel_name')} labelAlign="left" name="name">
|
<Form.Item label={t('field_channel_name')} labelAlign="left" name="name">
|
||||||
<Input
|
<Input
|
||||||
disabled={editing}
|
disabled={editing}
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
@ -69,9 +68,9 @@ function FormAlertChannels({
|
|||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem label={t('field_channel_type')} labelAlign="left" name="type">
|
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
||||||
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
||||||
<Option value="slack" key="slack">
|
<Option value="slack" key="slack">
|
||||||
Slack
|
Slack
|
||||||
@ -83,11 +82,11 @@ function FormAlertChannels({
|
|||||||
Pagerduty
|
Pagerduty
|
||||||
</Option>
|
</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
|
|
||||||
<FormItem>{renderSettings()}</FormItem>
|
<Form.Item>{renderSettings()}</Form.Item>
|
||||||
|
|
||||||
<FormItem>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
disabled={savingState}
|
disabled={savingState}
|
||||||
loading={savingState}
|
loading={savingState}
|
||||||
@ -110,7 +109,7 @@ function FormAlertChannels({
|
|||||||
>
|
>
|
||||||
{t('button_return')}
|
{t('button_return')}
|
||||||
</Button>
|
</Button>
|
||||||
</FormItem>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -20,12 +20,14 @@ function ChannelSelect({
|
|||||||
|
|
||||||
const { loading, payload, error, errorMessage } = useFetch(getChannels);
|
const { loading, payload, error, errorMessage } = useFetch(getChannels);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const handleChange = (value: string[]): void => {
|
const handleChange = (value: string[]): void => {
|
||||||
onSelectChannels(value);
|
onSelectChannels(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error && errorMessage !== '') {
|
if (error && errorMessage !== '') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: errorMessage,
|
description: errorMessage,
|
||||||
});
|
});
|
||||||
@ -48,19 +50,22 @@ function ChannelSelect({
|
|||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<StyledSelect
|
<>
|
||||||
status={error ? 'error' : ''}
|
{NotificationElement}
|
||||||
mode="multiple"
|
<StyledSelect
|
||||||
style={{ width: '100%' }}
|
status={error ? 'error' : ''}
|
||||||
placeholder={t('placeholder_channel_select')}
|
mode="multiple"
|
||||||
value={currentValue}
|
style={{ width: '100%' }}
|
||||||
onChange={(value): void => {
|
placeholder={t('placeholder_channel_select')}
|
||||||
handleChange(value as string[]);
|
value={currentValue}
|
||||||
}}
|
onChange={(value): void => {
|
||||||
optionLabelProp="label"
|
handleChange(value as string[]);
|
||||||
>
|
}}
|
||||||
{renderOptions()}
|
optionLabelProp="label"
|
||||||
</StyledSelect>
|
>
|
||||||
|
{renderOptions()}
|
||||||
|
</StyledSelect>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,10 +163,11 @@ function QuerySection({
|
|||||||
...allQueries,
|
...allQueries,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const addMetricQuery = useCallback(() => {
|
const addMetricQuery = useCallback(() => {
|
||||||
if (Object.keys(metricQueries).length > 5) {
|
if (Object.keys(metricQueries).length > 5) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('metric_query_max_limit'),
|
message: t('metric_query_max_limit'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -191,7 +192,7 @@ function QuerySection({
|
|||||||
expression: queryLabel,
|
expression: queryLabel,
|
||||||
};
|
};
|
||||||
setMetricQueries({ ...queries });
|
setMetricQueries({ ...queries });
|
||||||
}, [t, getNextQueryLabel, metricQueries, setMetricQueries]);
|
}, [t, getNextQueryLabel, metricQueries, setMetricQueries, notifications]);
|
||||||
|
|
||||||
const addFormula = useCallback(() => {
|
const addFormula = useCallback(() => {
|
||||||
// defaulting to F1 as only one formula is supported
|
// defaulting to F1 as only one formula is supported
|
||||||
@ -350,6 +351,7 @@ function QuerySection({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<div style={{ display: 'flex' }}>{renderTabs(alertType)}</div>
|
<div style={{ display: 'flex' }}>{renderTabs(alertType)}</div>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Select, Typography } from 'antd';
|
import { Form, Select, Typography } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -18,6 +17,7 @@ import {
|
|||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
function RuleOptions({
|
function RuleOptions({
|
||||||
alertDef,
|
alertDef,
|
||||||
|
@ -190,12 +190,14 @@ function FormAlertRules({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const validatePromParams = useCallback((): boolean => {
|
const validatePromParams = useCallback((): boolean => {
|
||||||
let retval = true;
|
let retval = true;
|
||||||
if (queryCategory !== EQueryType.PROM) return retval;
|
if (queryCategory !== EQueryType.PROM) return retval;
|
||||||
|
|
||||||
if (!promQueries || Object.keys(promQueries).length === 0) {
|
if (!promQueries || Object.keys(promQueries).length === 0) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('promql_required'),
|
description: t('promql_required'),
|
||||||
});
|
});
|
||||||
@ -204,7 +206,7 @@ function FormAlertRules({
|
|||||||
|
|
||||||
Object.keys(promQueries).forEach((key) => {
|
Object.keys(promQueries).forEach((key) => {
|
||||||
if (promQueries[key].query === '') {
|
if (promQueries[key].query === '') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('promql_required'),
|
description: t('promql_required'),
|
||||||
});
|
});
|
||||||
@ -213,14 +215,14 @@ function FormAlertRules({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}, [t, promQueries, queryCategory]);
|
}, [t, promQueries, queryCategory, notifications]);
|
||||||
|
|
||||||
const validateChQueryParams = useCallback((): boolean => {
|
const validateChQueryParams = useCallback((): boolean => {
|
||||||
let retval = true;
|
let retval = true;
|
||||||
if (queryCategory !== EQueryType.CLICKHOUSE) return retval;
|
if (queryCategory !== EQueryType.CLICKHOUSE) return retval;
|
||||||
|
|
||||||
if (!chQueries || Object.keys(chQueries).length === 0) {
|
if (!chQueries || Object.keys(chQueries).length === 0) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('chquery_required'),
|
description: t('chquery_required'),
|
||||||
});
|
});
|
||||||
@ -229,7 +231,7 @@ function FormAlertRules({
|
|||||||
|
|
||||||
Object.keys(chQueries).forEach((key) => {
|
Object.keys(chQueries).forEach((key) => {
|
||||||
if (chQueries[key].rawQuery === '') {
|
if (chQueries[key].rawQuery === '') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('chquery_required'),
|
description: t('chquery_required'),
|
||||||
});
|
});
|
||||||
@ -238,14 +240,14 @@ function FormAlertRules({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}, [t, chQueries, queryCategory]);
|
}, [t, chQueries, queryCategory, notifications]);
|
||||||
|
|
||||||
const validateQBParams = useCallback((): boolean => {
|
const validateQBParams = useCallback((): boolean => {
|
||||||
let retval = true;
|
let retval = true;
|
||||||
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
|
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
|
||||||
|
|
||||||
if (!metricQueries || Object.keys(metricQueries).length === 0) {
|
if (!metricQueries || Object.keys(metricQueries).length === 0) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('condition_required'),
|
description: t('condition_required'),
|
||||||
});
|
});
|
||||||
@ -253,7 +255,7 @@ function FormAlertRules({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!alertDef.condition?.target) {
|
if (!alertDef.condition?.target) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('target_missing'),
|
description: t('target_missing'),
|
||||||
});
|
});
|
||||||
@ -262,7 +264,7 @@ function FormAlertRules({
|
|||||||
|
|
||||||
Object.keys(metricQueries).forEach((key) => {
|
Object.keys(metricQueries).forEach((key) => {
|
||||||
if (metricQueries[key].metricName === '') {
|
if (metricQueries[key].metricName === '') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('metricname_missing', { where: metricQueries[key].name }),
|
description: t('metricname_missing', { where: metricQueries[key].name }),
|
||||||
});
|
});
|
||||||
@ -272,7 +274,7 @@ function FormAlertRules({
|
|||||||
|
|
||||||
Object.keys(formulaQueries).forEach((key) => {
|
Object.keys(formulaQueries).forEach((key) => {
|
||||||
if (formulaQueries[key].expression === '') {
|
if (formulaQueries[key].expression === '') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('expression_missing', formulaQueries[key].name),
|
description: t('expression_missing', formulaQueries[key].name),
|
||||||
});
|
});
|
||||||
@ -280,11 +282,11 @@ function FormAlertRules({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return retval;
|
return retval;
|
||||||
}, [t, alertDef, queryCategory, metricQueries, formulaQueries]);
|
}, [t, alertDef, queryCategory, metricQueries, formulaQueries, notifications]);
|
||||||
|
|
||||||
const isFormValid = useCallback((): boolean => {
|
const isFormValid = useCallback((): boolean => {
|
||||||
if (!alertDef.alert || alertDef.alert === '') {
|
if (!alertDef.alert || alertDef.alert === '') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('alertname_required'),
|
description: t('alertname_required'),
|
||||||
});
|
});
|
||||||
@ -300,7 +302,14 @@ function FormAlertRules({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return validateQBParams();
|
return validateQBParams();
|
||||||
}, [t, validateQBParams, validateChQueryParams, alertDef, validatePromParams]);
|
}, [
|
||||||
|
t,
|
||||||
|
validateQBParams,
|
||||||
|
validateChQueryParams,
|
||||||
|
alertDef,
|
||||||
|
validatePromParams,
|
||||||
|
notifications,
|
||||||
|
]);
|
||||||
|
|
||||||
const preparePostData = (): AlertDef => {
|
const preparePostData = (): AlertDef => {
|
||||||
const postableAlert: AlertDef = {
|
const postableAlert: AlertDef = {
|
||||||
@ -348,7 +357,7 @@ function FormAlertRules({
|
|||||||
const response = await saveAlertApi(apiReq);
|
const response = await saveAlertApi(apiReq);
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
description:
|
description:
|
||||||
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
|
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
|
||||||
@ -361,19 +370,26 @@ function FormAlertRules({
|
|||||||
history.replace(ROUTES.LIST_ALL_ALERT);
|
history.replace(ROUTES.LIST_ALL_ALERT);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: response.error || t('unexpected_error'),
|
description: response.error || t('unexpected_error'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('unexpected_error'),
|
description: t('unexpected_error'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, [t, isFormValid, ruleId, ruleCache, memoizedPreparePostData]);
|
}, [
|
||||||
|
t,
|
||||||
|
isFormValid,
|
||||||
|
ruleId,
|
||||||
|
ruleCache,
|
||||||
|
memoizedPreparePostData,
|
||||||
|
notifications,
|
||||||
|
]);
|
||||||
|
|
||||||
const onSaveHandler = useCallback(async () => {
|
const onSaveHandler = useCallback(async () => {
|
||||||
const content = (
|
const content = (
|
||||||
@ -407,30 +423,30 @@ function FormAlertRules({
|
|||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
const { payload } = response;
|
const { payload } = response;
|
||||||
if (payload?.alertCount === 0) {
|
if (payload?.alertCount === 0) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('no_alerts_found'),
|
description: t('no_alerts_found'),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('rule_test_fired'),
|
description: t('rule_test_fired'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: response.error || t('unexpected_error'),
|
description: response.error || t('unexpected_error'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('unexpected_error'),
|
description: t('unexpected_error'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, [t, isFormValid, memoizedPreparePostData]);
|
}, [t, isFormValid, memoizedPreparePostData, notifications]);
|
||||||
|
|
||||||
const renderBasicInfo = (): JSX.Element => (
|
const renderBasicInfo = (): JSX.Element => (
|
||||||
<BasicInfo alertDef={alertDef} setAlertDef={setAlertDef} />
|
<BasicInfo alertDef={alertDef} setAlertDef={setAlertDef} />
|
||||||
@ -467,6 +483,7 @@ function FormAlertRules({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
{Element}
|
{Element}
|
||||||
<PanelContainer>
|
<PanelContainer>
|
||||||
<StyledLeftContainer flex="5 1 600px">
|
<StyledLeftContainer flex="5 1 600px">
|
||||||
|
@ -95,6 +95,8 @@ export const ThresholdInput = styled(InputNumber)`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
& > .ant-input-number-group-addon {
|
& > .ant-input-number-group-addon {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
& > .ant-input-number {
|
& > .ant-input-number {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
@ -172,6 +172,8 @@ function GeneralSettings({
|
|||||||
logsTtlValuesPayload.status === 'pending' ? 1000 : null,
|
logsTtlValuesPayload.status === 'pending' ? 1000 : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onModalToggleHandler = (type: TTTLType): void => {
|
const onModalToggleHandler = (type: TTTLType): void => {
|
||||||
if (type === 'metrics') setModalMetrics((modal) => !modal);
|
if (type === 'metrics') setModalMetrics((modal) => !modal);
|
||||||
if (type === 'traces') setModalTraces((modal) => !modal);
|
if (type === 'traces') setModalTraces((modal) => !modal);
|
||||||
@ -186,14 +188,14 @@ function GeneralSettings({
|
|||||||
const onClickSaveHandler = useCallback(
|
const onClickSaveHandler = useCallback(
|
||||||
(type: TTTLType) => {
|
(type: TTTLType) => {
|
||||||
if (!setRetentionPermission) {
|
if (!setRetentionPermission) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: `Sorry you don't have permission to make these changes`,
|
message: `Sorry you don't have permission to make these changes`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onModalToggleHandler(type);
|
onModalToggleHandler(type);
|
||||||
},
|
},
|
||||||
[setRetentionPermission],
|
[setRetentionPermission, notifications],
|
||||||
);
|
);
|
||||||
|
|
||||||
const s3Enabled = useMemo(
|
const s3Enabled = useMemo(
|
||||||
@ -352,7 +354,7 @@ function GeneralSettings({
|
|||||||
let hasSetTTLFailed = false;
|
let hasSetTTLFailed = false;
|
||||||
if (setTTLResponse.statusCode === 409) {
|
if (setTTLResponse.statusCode === 409) {
|
||||||
hasSetTTLFailed = true;
|
hasSetTTLFailed = true;
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('retention_request_race_condition'),
|
description: t('retention_request_race_condition'),
|
||||||
placement: 'topRight',
|
placement: 'topRight',
|
||||||
@ -390,7 +392,7 @@ function GeneralSettings({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('retention_failed_message'),
|
description: t('retention_failed_message'),
|
||||||
placement: 'topRight',
|
placement: 'topRight',
|
||||||
@ -591,6 +593,7 @@ function GeneralSettings({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
{Element}
|
{Element}
|
||||||
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
||||||
<ErrorTextContainer>
|
<ErrorTextContainer>
|
||||||
|
@ -43,6 +43,7 @@ function GridCardGraph({
|
|||||||
const { ref: graphRef, inView: isGraphVisible } = useInView({
|
const { ref: graphRef, inView: isGraphVisible } = useInView({
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
triggerOnce: true,
|
triggerOnce: true,
|
||||||
|
initialInView: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
|
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
|
||||||
|
@ -204,6 +204,8 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
[widgets, onDragSelect],
|
[widgets, onDragSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onEmptyWidgetHandler = useCallback(async () => {
|
const onEmptyWidgetHandler = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const id = 'empty';
|
const id = 'empty';
|
||||||
@ -219,22 +221,25 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
...(data.layout || []),
|
...(data.layout || []),
|
||||||
];
|
];
|
||||||
|
|
||||||
await UpdateDashboard({
|
await UpdateDashboard(
|
||||||
data,
|
{
|
||||||
generateWidgetId: id,
|
data,
|
||||||
graphType: 'EMPTY_WIDGET',
|
generateWidgetId: id,
|
||||||
selectedDashboard,
|
graphType: 'EMPTY_WIDGET',
|
||||||
layout,
|
selectedDashboard,
|
||||||
isRedirected: false,
|
layout,
|
||||||
});
|
isRedirected: false,
|
||||||
|
},
|
||||||
|
notifications,
|
||||||
|
);
|
||||||
|
|
||||||
setLayoutFunction(layout);
|
setLayoutFunction(layout);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: error instanceof Error ? error.toString() : 'Something went wrong',
|
message: error instanceof Error ? error.toString() : 'Something went wrong',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data, selectedDashboard, setLayoutFunction]);
|
}, [data, selectedDashboard, setLayoutFunction, notifications]);
|
||||||
|
|
||||||
const onLayoutChangeHandler = async (layout: Layout[]): Promise<void> => {
|
const onLayoutChangeHandler = async (layout: Layout[]): Promise<void> => {
|
||||||
setLayoutFunction(layout);
|
setLayoutFunction(layout);
|
||||||
@ -255,7 +260,7 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
toggleAddWidget(true);
|
toggleAddWidget(true);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
notification.error(t('something_went_wrong'));
|
notifications.error(t('something_went_wrong'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toggleAddWidget(true);
|
toggleAddWidget(true);
|
||||||
@ -263,26 +268,29 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: error || t('something_went_wrong'),
|
message: error || t('something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [layouts, onEmptyWidgetHandler, t, toggleAddWidget]);
|
}, [layouts, onEmptyWidgetHandler, t, toggleAddWidget, notifications]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GraphLayoutContainer
|
<>
|
||||||
{...{
|
{NotificationElement}
|
||||||
addPanelLoading,
|
<GraphLayoutContainer
|
||||||
layouts,
|
{...{
|
||||||
onAddPanelHandler,
|
addPanelLoading,
|
||||||
onLayoutChangeHandler,
|
layouts,
|
||||||
onLayoutSaveHandler,
|
onAddPanelHandler,
|
||||||
saveLayoutState,
|
onLayoutChangeHandler,
|
||||||
widgets,
|
onLayoutSaveHandler,
|
||||||
setLayout,
|
saveLayoutState,
|
||||||
}}
|
widgets,
|
||||||
/>
|
setLayout,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { notification } from 'antd';
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
import updateDashboardApi from 'api/dashboard/update';
|
import updateDashboardApi from 'api/dashboard/update';
|
||||||
import {
|
import {
|
||||||
ClickHouseQueryTemplate,
|
ClickHouseQueryTemplate,
|
||||||
@ -12,14 +12,17 @@ import store from 'store';
|
|||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
|
|
||||||
export const UpdateDashboard = async ({
|
export const UpdateDashboard = async (
|
||||||
data,
|
{
|
||||||
graphType,
|
data,
|
||||||
generateWidgetId,
|
graphType,
|
||||||
layout,
|
generateWidgetId,
|
||||||
selectedDashboard,
|
layout,
|
||||||
isRedirected,
|
selectedDashboard,
|
||||||
}: UpdateDashboardProps): Promise<Dashboard | undefined> => {
|
isRedirected,
|
||||||
|
}: UpdateDashboardProps,
|
||||||
|
notify: NotificationInstance,
|
||||||
|
): Promise<Dashboard | undefined> => {
|
||||||
const updatedSelectedDashboard: Dashboard = {
|
const updatedSelectedDashboard: Dashboard = {
|
||||||
...selectedDashboard,
|
...selectedDashboard,
|
||||||
data: {
|
data: {
|
||||||
@ -89,7 +92,7 @@ export const UpdateDashboard = async ({
|
|||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
return response.payload;
|
return response.payload;
|
||||||
}
|
}
|
||||||
notification.error({
|
notify.error({
|
||||||
message: response.error || 'Something went wrong',
|
message: response.error || 'Something went wrong',
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Button, Input, notification } from 'antd';
|
import { Button, Form, Input, notification } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
||||||
import apply from 'api/licenses/apply';
|
import apply from 'api/licenses/apply';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
@ -13,6 +12,8 @@ import { PayloadProps } from 'types/api/licenses/getAll';
|
|||||||
|
|
||||||
import { ApplyForm, ApplyFormContainer, LicenseInput } from './styles';
|
import { ApplyForm, ApplyFormContainer, LicenseInput } from './styles';
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
function ApplyLicenseForm({
|
function ApplyLicenseForm({
|
||||||
licenseRefetch,
|
licenseRefetch,
|
||||||
}: ApplyLicenseFormProps): JSX.Element {
|
}: ApplyLicenseFormProps): JSX.Element {
|
||||||
@ -26,10 +27,12 @@ function ApplyLicenseForm({
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onFinish = async (values: unknown | { key: string }): Promise<void> => {
|
const onFinish = async (values: unknown | { key: string }): Promise<void> => {
|
||||||
const params = values as { key: string };
|
const params = values as { key: string };
|
||||||
if (params.key === '' || !params.key) {
|
if (params.key === '' || !params.key) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('enter_license_key'),
|
description: t('enter_license_key'),
|
||||||
});
|
});
|
||||||
@ -53,18 +56,18 @@ function ApplyLicenseForm({
|
|||||||
payload: featureFlagsResponse.data.payload,
|
payload: featureFlagsResponse.data.payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('license_applied'),
|
description: t('license_applied'),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: response.error || t('unexpected_error'),
|
description: response.error || t('unexpected_error'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('unexpected_error'),
|
description: t('unexpected_error'),
|
||||||
});
|
});
|
||||||
@ -74,6 +77,7 @@ function ApplyLicenseForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ApplyFormContainer>
|
<ApplyFormContainer>
|
||||||
|
{NotificationElement}
|
||||||
<ApplyForm layout="inline" onFinish={onFinish}>
|
<ApplyForm layout="inline" onFinish={onFinish}>
|
||||||
<LicenseInput labelAlign="left" name="key">
|
<LicenseInput labelAlign="left" name="key">
|
||||||
<Input
|
<Input
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Table } from 'antd';
|
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { License } from 'types/api/licenses/def';
|
import { License } from 'types/api/licenses/def';
|
||||||
@ -13,25 +13,29 @@ function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
|
|||||||
title: t('column_license_status'),
|
title: t('column_license_status'),
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('column_license_key'),
|
title: t('column_license_key'),
|
||||||
dataIndex: 'key',
|
dataIndex: 'key',
|
||||||
key: 'key',
|
key: 'key',
|
||||||
|
width: 80,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('column_valid_from'),
|
title: t('column_valid_from'),
|
||||||
dataIndex: 'ValidFrom',
|
dataIndex: 'ValidFrom',
|
||||||
key: 'valid from',
|
key: 'valid from',
|
||||||
|
width: 80,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('column_valid_until'),
|
title: t('column_valid_until'),
|
||||||
dataIndex: 'ValidUntil',
|
dataIndex: 'ValidUntil',
|
||||||
key: 'valid until',
|
key: 'valid until',
|
||||||
|
width: 80,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return <Table rowKey="id" dataSource={licenses} columns={columns} />;
|
return <ResizeTable columns={columns} rowKey="id" dataSource={licenses} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ListLicensesProps {
|
interface ListLicensesProps {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const ApplyFormContainer = styled.div`
|
export const ApplyFormContainer = styled.div`
|
||||||
@ -15,7 +14,7 @@ export const ApplyForm = styled(Form)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LicenseInput = styled(FormItem)`
|
export const LicenseInput = styled(Form.Item)`
|
||||||
width: 200px;
|
width: 200px;
|
||||||
&:focus {
|
&:focus {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { notification, Table, Typography } from 'antd';
|
import { notification, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
@ -30,6 +31,8 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
role,
|
role,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [notificationsApi, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
const { data: refetchData, status } = await refetch();
|
const { data: refetchData, status } = await refetch();
|
||||||
@ -37,7 +40,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
setData(refetchData?.payload || []);
|
setData(refetchData?.payload || []);
|
||||||
}
|
}
|
||||||
if (status === 'error') {
|
if (status === 'error') {
|
||||||
notification.error({
|
notificationsApi.error({
|
||||||
message: t('something_went_wrong'),
|
message: t('something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -58,6 +61,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
dataIndex: 'state',
|
dataIndex: 'state',
|
||||||
|
width: 80,
|
||||||
key: 'state',
|
key: 'state',
|
||||||
sorter: (a, b): number =>
|
sorter: (a, b): number =>
|
||||||
(b.state ? b.state.charCodeAt(0) : 1000) -
|
(b.state ? b.state.charCodeAt(0) : 1000) -
|
||||||
@ -67,6 +71,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Alert Name',
|
title: 'Alert Name',
|
||||||
dataIndex: 'alert',
|
dataIndex: 'alert',
|
||||||
|
width: 100,
|
||||||
key: 'name',
|
key: 'name',
|
||||||
sorter: (a, b): number =>
|
sorter: (a, b): number =>
|
||||||
(a.alert ? a.alert.charCodeAt(0) : 1000) -
|
(a.alert ? a.alert.charCodeAt(0) : 1000) -
|
||||||
@ -82,6 +87,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Severity',
|
title: 'Severity',
|
||||||
dataIndex: 'labels',
|
dataIndex: 'labels',
|
||||||
|
width: 80,
|
||||||
key: 'severity',
|
key: 'severity',
|
||||||
sorter: (a, b): number =>
|
sorter: (a, b): number =>
|
||||||
(a.labels ? a.labels.severity.length : 0) -
|
(a.labels ? a.labels.severity.length : 0) -
|
||||||
@ -99,7 +105,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
dataIndex: 'labels',
|
dataIndex: 'labels',
|
||||||
key: 'tags',
|
key: 'tags',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 350,
|
width: 100,
|
||||||
render: (value): JSX.Element => {
|
render: (value): JSX.Element => {
|
||||||
const objectKeys = Object.keys(value);
|
const objectKeys = Object.keys(value);
|
||||||
const withOutSeverityKeys = objectKeys.filter((e) => e !== 'severity');
|
const withOutSeverityKeys = objectKeys.filter((e) => e !== 'severity');
|
||||||
@ -126,6 +132,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
title: 'Action',
|
title: 'Action',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
width: 120,
|
||||||
render: (id: GettableAlert['id'], record): JSX.Element => (
|
render: (id: GettableAlert['id'], record): JSX.Element => (
|
||||||
<>
|
<>
|
||||||
<ToggleAlertState disabled={record.disabled} setData={setData} id={id} />
|
<ToggleAlertState disabled={record.disabled} setData={setData} id={id} />
|
||||||
@ -145,6 +152,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
{Element}
|
{Element}
|
||||||
|
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
@ -161,8 +169,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
|
<ResizeTable columns={columns} rowKey="id" dataSource={data} />
|
||||||
<Table rowKey="id" columns={columns} dataSource={data} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ function ToggleAlertState({
|
|||||||
payload: undefined,
|
payload: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const defaultErrorMessage = 'Something went wrong';
|
const defaultErrorMessage = 'Something went wrong';
|
||||||
|
|
||||||
const onToggleHandler = async (
|
const onToggleHandler = async (
|
||||||
@ -58,7 +60,7 @@ function ToggleAlertState({
|
|||||||
loading: false,
|
loading: false,
|
||||||
payload: response.payload,
|
payload: response.payload,
|
||||||
}));
|
}));
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -69,7 +71,7 @@ function ToggleAlertState({
|
|||||||
errorMessage: response.error || defaultErrorMessage,
|
errorMessage: response.error || defaultErrorMessage,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: response.error || defaultErrorMessage,
|
message: response.error || defaultErrorMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -81,21 +83,24 @@ function ToggleAlertState({
|
|||||||
errorMessage: defaultErrorMessage,
|
errorMessage: defaultErrorMessage,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: defaultErrorMessage,
|
message: defaultErrorMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColumnButton
|
<>
|
||||||
disabled={apiStatus.loading || false}
|
{NotificationElement}
|
||||||
loading={apiStatus.loading || false}
|
<ColumnButton
|
||||||
onClick={(): Promise<void> => onToggleHandler(id, !disabled)}
|
disabled={apiStatus.loading || false}
|
||||||
type="link"
|
loading={apiStatus.loading || false}
|
||||||
>
|
onClick={(): Promise<void> => onToggleHandler(id, !disabled)}
|
||||||
{disabled ? 'Enable' : 'Disable'}
|
type="link"
|
||||||
</ColumnButton>
|
>
|
||||||
|
{disabled ? 'Enable' : 'Disable'}
|
||||||
|
</ColumnButton>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,28 +17,38 @@ function ListAlertRules(): JSX.Element {
|
|||||||
cacheTime: 0,
|
cacheTime: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: data?.error || t('something_went_wrong'),
|
message: data?.error || t('something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data?.error, data?.statusCode, status, t]);
|
}, [data?.error, data?.statusCode, status, t, notifications]);
|
||||||
|
|
||||||
// api failed to load the data
|
// api failed to load the data
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <div>{data?.error || t('something_went_wrong')}</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
{NotificationElement}
|
||||||
|
{data?.error || t('something_went_wrong')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// api is successful but error is present
|
// api is successful but error is present
|
||||||
if (status === 'success' && data.statusCode >= 400) {
|
if (status === 'success' && data.statusCode >= 400) {
|
||||||
return (
|
return (
|
||||||
<ListAlert
|
<>
|
||||||
{...{
|
{NotificationElement}
|
||||||
allAlertRules: [],
|
<ListAlert
|
||||||
refetch,
|
{...{
|
||||||
}}
|
allAlertRules: [],
|
||||||
/>
|
refetch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +59,7 @@ function ListAlertRules(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||||
|
{NotificationElement}
|
||||||
<ReleaseNote path={location.pathname} />
|
<ReleaseNote path={location.pathname} />
|
||||||
<ListAlert
|
<ListAlert
|
||||||
{...{
|
{...{
|
||||||
|
@ -41,6 +41,8 @@ function ImportJSON({
|
|||||||
|
|
||||||
const [editorValue, setEditorValue] = useState<string>('');
|
const [editorValue, setEditorValue] = useState<string>('');
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onChangeHandler: UploadProps['onChange'] = (info) => {
|
const onChangeHandler: UploadProps['onChange'] = (info) => {
|
||||||
const { fileList } = info;
|
const { fileList } = info;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@ -106,7 +108,7 @@ function ImportJSON({
|
|||||||
}, 10);
|
}, 10);
|
||||||
} else {
|
} else {
|
||||||
setIsCreateDashboardError(true);
|
setIsCreateDashboardError(true);
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
response.error ||
|
response.error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -130,58 +132,61 @@ function ImportJSON({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<>
|
||||||
open={isImportJSONModalVisible}
|
{NotificationElement}
|
||||||
centered
|
<Modal
|
||||||
maskClosable
|
open={isImportJSONModalVisible}
|
||||||
destroyOnClose
|
centered
|
||||||
width="70vw"
|
maskClosable
|
||||||
onCancel={onModalHandler}
|
destroyOnClose
|
||||||
title={
|
width="70vw"
|
||||||
<>
|
onCancel={onModalHandler}
|
||||||
<Typography.Title level={4}>{t('import_json')}</Typography.Title>
|
title={
|
||||||
<Typography>{t('import_dashboard_by_pasting')}</Typography>
|
<>
|
||||||
</>
|
<Typography.Title level={4}>{t('import_json')}</Typography.Title>
|
||||||
}
|
<Typography>{t('import_dashboard_by_pasting')}</Typography>
|
||||||
footer={
|
</>
|
||||||
<FooterContainer>
|
}
|
||||||
<Button
|
footer={
|
||||||
disabled={editorValue.length === 0}
|
<FooterContainer>
|
||||||
onClick={onClickLoadJsonHandler}
|
<Button
|
||||||
loading={dashboardCreating}
|
disabled={editorValue.length === 0}
|
||||||
>
|
onClick={onClickLoadJsonHandler}
|
||||||
{t('load_json')}
|
loading={dashboardCreating}
|
||||||
</Button>
|
>
|
||||||
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
|
{t('load_json')}
|
||||||
</FooterContainer>
|
</Button>
|
||||||
}
|
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
|
||||||
>
|
</FooterContainer>
|
||||||
<div>
|
}
|
||||||
<Space direction="horizontal">
|
>
|
||||||
<Upload
|
<div>
|
||||||
accept=".json"
|
<Space direction="horizontal">
|
||||||
showUploadList={false}
|
<Upload
|
||||||
multiple={false}
|
accept=".json"
|
||||||
onChange={onChangeHandler}
|
showUploadList={false}
|
||||||
beforeUpload={(): boolean => false}
|
multiple={false}
|
||||||
action="none"
|
onChange={onChangeHandler}
|
||||||
data={jsonData}
|
beforeUpload={(): boolean => false}
|
||||||
>
|
action="none"
|
||||||
<Button type="primary">{t('upload_json_file')}</Button>
|
data={jsonData}
|
||||||
</Upload>
|
>
|
||||||
{isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}</>}
|
<Button type="primary">{t('upload_json_file')}</Button>
|
||||||
</Space>
|
</Upload>
|
||||||
|
{isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}</>}
|
||||||
|
</Space>
|
||||||
|
|
||||||
<EditorContainer>
|
<EditorContainer>
|
||||||
<Typography.Paragraph>{t('paste_json_below')}</Typography.Paragraph>
|
<Typography.Paragraph>{t('paste_json_below')}</Typography.Paragraph>
|
||||||
<Editor
|
<Editor
|
||||||
onChange={(newValue): void => setEditorValue(newValue)}
|
onChange={(newValue): void => setEditorValue(newValue)}
|
||||||
value={editorValue}
|
value={editorValue}
|
||||||
language="json"
|
language="json"
|
||||||
/>
|
/>
|
||||||
</EditorContainer>
|
</EditorContainer>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import {
|
import { Card, Dropdown, Menu, Row, TableColumnProps, Typography } from 'antd';
|
||||||
Card,
|
|
||||||
Dropdown,
|
|
||||||
Menu,
|
|
||||||
Row,
|
|
||||||
Table,
|
|
||||||
TableColumnProps,
|
|
||||||
Typography,
|
|
||||||
} from 'antd';
|
|
||||||
import createDashboard from 'api/dashboard/create';
|
import createDashboard from 'api/dashboard/create';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import SearchFilter from 'container/ListOfDashboard/SearchFilter';
|
import SearchFilter from 'container/ListOfDashboard/SearchFilter';
|
||||||
@ -74,20 +67,24 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
|
width: 100,
|
||||||
render: Name,
|
render: Name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
|
width: 100,
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tags (can be multiple)',
|
title: 'Tags (can be multiple)',
|
||||||
dataIndex: 'tags',
|
dataIndex: 'tags',
|
||||||
|
width: 80,
|
||||||
render: Tags,
|
render: Tags,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Created At',
|
title: 'Created At',
|
||||||
dataIndex: 'createdBy',
|
dataIndex: 'createdBy',
|
||||||
|
width: 80,
|
||||||
sorter: (a: Data, b: Data): number => {
|
sorter: (a: Data, b: Data): number => {
|
||||||
const prev = new Date(a.createdBy).getTime();
|
const prev = new Date(a.createdBy).getTime();
|
||||||
const next = new Date(b.createdBy).getTime();
|
const next = new Date(b.createdBy).getTime();
|
||||||
@ -98,6 +95,7 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Last Updated Time',
|
title: 'Last Updated Time',
|
||||||
|
width: 90,
|
||||||
dataIndex: 'lastUpdatedTime',
|
dataIndex: 'lastUpdatedTime',
|
||||||
sorter: (a: Data, b: Data): number => {
|
sorter: (a: Data, b: Data): number => {
|
||||||
const prev = new Date(a.lastUpdatedTime).getTime();
|
const prev = new Date(a.lastUpdatedTime).getTime();
|
||||||
@ -114,6 +112,7 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
title: 'Action',
|
title: 'Action',
|
||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
key: 'x',
|
key: 'x',
|
||||||
|
width: 40,
|
||||||
render: DeleteButton,
|
render: DeleteButton,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -271,7 +270,8 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
uploadedGrafana={uploadedGrafana}
|
uploadedGrafana={uploadedGrafana}
|
||||||
onModalHandler={(): void => onModalHandler(false)}
|
onModalHandler={(): void => onModalHandler(false)}
|
||||||
/>
|
/>
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
pagination={{
|
pagination={{
|
||||||
pageSize: 9,
|
pageSize: 9,
|
||||||
defaultPageSize: 9,
|
defaultPageSize: 9,
|
||||||
@ -280,7 +280,6 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
bordered
|
bordered
|
||||||
sticky
|
sticky
|
||||||
loading={loading}
|
loading={loading}
|
||||||
columns={columns}
|
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
showSorterTooltip
|
showSorterTooltip
|
||||||
/>
|
/>
|
||||||
|
@ -4,6 +4,8 @@ import {
|
|||||||
RightOutlined,
|
RightOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Button, Divider, Select } from 'antd';
|
import { Button, Divider, Select } from 'antd';
|
||||||
|
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||||
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -13,6 +15,7 @@ import {
|
|||||||
RESET_ID_START_AND_END,
|
RESET_ID_START_AND_END,
|
||||||
SET_LOG_LINES_PER_PAGE,
|
SET_LOG_LINES_PER_PAGE,
|
||||||
} from 'types/actions/logs';
|
} from 'types/actions/logs';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE_OPTIONS } from './config';
|
import { ITEMS_PER_PAGE_OPTIONS } from './config';
|
||||||
@ -28,18 +31,33 @@ function LogControls(): JSX.Element | null {
|
|||||||
isLoadingAggregate,
|
isLoadingAggregate,
|
||||||
logs,
|
logs,
|
||||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||||
|
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const handleLogLinesPerPageChange = (e: number): void => {
|
const handleLogLinesPerPageChange = (e: number): void => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_LOG_LINES_PER_PAGE,
|
type: SET_LOG_LINES_PER_PAGE,
|
||||||
payload: e,
|
payload: {
|
||||||
|
logLinesPerPage: e,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoToLatest = (): void => {
|
const handleGoToLatest = (): void => {
|
||||||
|
const { maxTime, minTime } = getMinMax(
|
||||||
|
globalTime.selectedTime,
|
||||||
|
globalTime.minTime,
|
||||||
|
globalTime.maxTime,
|
||||||
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: RESET_ID_START_AND_END,
|
type: RESET_ID_START_AND_END,
|
||||||
|
payload: getGlobalTime(globalTime.selectedTime, {
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import getStep from 'lib/getStep';
|
|||||||
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { getLogs } from 'store/actions/logs/getLogs';
|
import { getLogs } from 'store/actions/logs/getLogs';
|
||||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||||
@ -46,7 +46,7 @@ function ActionItem({
|
|||||||
liveTail,
|
liveTail,
|
||||||
idEnd,
|
idEnd,
|
||||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
@ -62,7 +62,9 @@ function ActionItem({
|
|||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_SEARCH_QUERY_STRING,
|
type: SET_SEARCH_QUERY_STRING,
|
||||||
payload: updatedQueryString,
|
payload: {
|
||||||
|
searchQueryString: updatedQueryString,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (liveTail === 'STOPPED') {
|
if (liveTail === 'STOPPED') {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { blue, orange } from '@ant-design/colors';
|
import { blue, orange } from '@ant-design/colors';
|
||||||
import { Input, Table } from 'antd';
|
import { Input } from 'antd';
|
||||||
import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
|
import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
|
||||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import flatten from 'flat';
|
import flatten from 'flat';
|
||||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
@ -56,7 +57,7 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
title: 'Field',
|
title: 'Field',
|
||||||
dataIndex: 'field',
|
dataIndex: 'field',
|
||||||
key: 'field',
|
key: 'field',
|
||||||
width: '35%',
|
width: 100,
|
||||||
render: (field: string): JSX.Element => {
|
render: (field: string): JSX.Element => {
|
||||||
const fieldKey = field.split('.').slice(-1);
|
const fieldKey = field.split('.').slice(-1);
|
||||||
const renderedField = <span style={{ color: blue[4] }}>{field}</span>;
|
const renderedField = <span style={{ color: blue[4] }}>{field}</span>;
|
||||||
@ -64,7 +65,6 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
|
||||||
return (
|
return (
|
||||||
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
|
<AddToQueryHOC fieldKey={fieldKey[0]} fieldValue={flattenLogData[field]}>
|
||||||
{' '}
|
|
||||||
{renderedField}
|
{renderedField}
|
||||||
</AddToQueryHOC>
|
</AddToQueryHOC>
|
||||||
);
|
);
|
||||||
@ -76,15 +76,16 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
title: 'Value',
|
title: 'Value',
|
||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
key: 'value',
|
key: 'value',
|
||||||
|
width: 80,
|
||||||
ellipsis: false,
|
ellipsis: false,
|
||||||
render: (field: never): JSX.Element => (
|
render: (field: never): JSX.Element => (
|
||||||
<CopyClipboardHOC textToCopy={field}>
|
<CopyClipboardHOC textToCopy={field}>
|
||||||
<span style={{ color: orange[6] }}>{field}</span>
|
<span style={{ color: orange[6] }}>{field}</span>
|
||||||
</CopyClipboardHOC>
|
</CopyClipboardHOC>
|
||||||
),
|
),
|
||||||
width: '60%',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<Input
|
<Input
|
||||||
@ -93,11 +94,10 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
value={fieldSearchInput}
|
value={fieldSearchInput}
|
||||||
onChange={(e): void => setFieldSearchInput(e.target.value)}
|
onChange={(e): void => setFieldSearchInput(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Table
|
<ResizeTable
|
||||||
// scroll={{ x: true }}
|
columns={columns as never}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
columns={columns as never}
|
|
||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,6 +42,8 @@ function Login({
|
|||||||
const [precheckInProcess, setPrecheckInProcess] = useState(false);
|
const [precheckInProcess, setPrecheckInProcess] = useState(false);
|
||||||
const [precheckComplete, setPrecheckComplete] = useState(false);
|
const [precheckComplete, setPrecheckComplete] = useState(false);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (withPassword === 'Y') {
|
if (withPassword === 'Y') {
|
||||||
setPrecheckComplete(true);
|
setPrecheckComplete(true);
|
||||||
@ -62,15 +64,15 @@ function Login({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ssoerror !== '') {
|
if (ssoerror !== '') {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('failed_to_login'),
|
message: t('failed_to_login'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [ssoerror, t]);
|
}, [ssoerror, t, notifications]);
|
||||||
|
|
||||||
const onNextHandler = async (): Promise<void> => {
|
const onNextHandler = async (): Promise<void> => {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('invalid_email'),
|
message: t('invalid_email'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -88,18 +90,18 @@ function Login({
|
|||||||
if (isUser) {
|
if (isUser) {
|
||||||
setPrecheckComplete(true);
|
setPrecheckComplete(true);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('invalid_account'),
|
message: t('invalid_account'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('invalid_config'),
|
message: t('invalid_config'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('failed to call precheck Api', e);
|
console.log('failed to call precheck Api', e);
|
||||||
notification.error({ message: t('unexpected_error') });
|
notifications.error({ message: t('unexpected_error') });
|
||||||
}
|
}
|
||||||
setPrecheckInProcess(false);
|
setPrecheckInProcess(false);
|
||||||
};
|
};
|
||||||
@ -144,14 +146,14 @@ function Login({
|
|||||||
);
|
);
|
||||||
history.push(ROUTES.APPLICATION);
|
history.push(ROUTES.APPLICATION);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: response.error || t('unexpected_error'),
|
message: response.error || t('unexpected_error'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('unexpected_error'),
|
message: t('unexpected_error'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -183,6 +185,7 @@ function Login({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
|
{NotificationElement}
|
||||||
<FormContainer onSubmit={onSubmitHandler}>
|
<FormContainer onSubmit={onSubmitHandler}>
|
||||||
<Title level={4}>{t('login_page_title')}</Title>
|
<Title level={4}>{t('login_page_title')}</Title>
|
||||||
<ParentContainer>
|
<ParentContainer>
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { Button, Popover, Spin } from 'antd';
|
import { Button, Popover, Spin, Typography } from 'antd';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import React, { useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
IField,
|
||||||
|
IInterestingFields,
|
||||||
|
ISelectedFields,
|
||||||
|
} from 'types/api/logs/fields';
|
||||||
|
|
||||||
|
import { ICON_STYLE } from './config';
|
||||||
import { Field } from './styles';
|
import { Field } from './styles';
|
||||||
|
|
||||||
interface FieldItemProps {
|
function FieldItem({
|
||||||
name: string;
|
|
||||||
buttonIcon: React.ReactNode;
|
|
||||||
buttonOnClick: (arg0: Record<string, unknown>) => void;
|
|
||||||
fieldData: Record<string, never>;
|
|
||||||
fieldIndex: number;
|
|
||||||
isLoading: boolean;
|
|
||||||
iconHoverText: string;
|
|
||||||
}
|
|
||||||
export function FieldItem({
|
|
||||||
name,
|
name,
|
||||||
buttonIcon,
|
buttonIcon,
|
||||||
buttonOnClick,
|
buttonOnClick,
|
||||||
@ -23,33 +20,65 @@ export function FieldItem({
|
|||||||
isLoading,
|
isLoading,
|
||||||
iconHoverText,
|
iconHoverText,
|
||||||
}: FieldItemProps): JSX.Element {
|
}: FieldItemProps): JSX.Element {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const onClickHandler = useCallback(() => {
|
||||||
|
if (!isLoading && buttonOnClick) buttonOnClick({ fieldData, fieldIndex });
|
||||||
|
}, [buttonOnClick, fieldData, fieldIndex, isLoading]);
|
||||||
|
|
||||||
|
const renderContent = useMemo(() => {
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spin spinning size="small" indicator={<LoadingOutlined spin />} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHovered) {
|
||||||
|
return (
|
||||||
|
<Popover content={<Typography>{iconHoverText}</Typography>}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
icon={buttonIcon}
|
||||||
|
onClick={onClickHandler}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [buttonIcon, iconHoverText, isHovered, isLoading, onClickHandler]);
|
||||||
|
|
||||||
|
const onMouseHoverHandler = useCallback(
|
||||||
|
(value: boolean) => (): void => {
|
||||||
|
setIsHovered(value);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
onMouseEnter={(): void => {
|
onMouseEnter={onMouseHoverHandler(true)}
|
||||||
setIsHovered(true);
|
onMouseLeave={onMouseHoverHandler(false)}
|
||||||
}}
|
|
||||||
onMouseLeave={(): void => setIsHovered(false)}
|
|
||||||
isDarkMode={isDarkMode}
|
isDarkMode={isDarkMode}
|
||||||
>
|
>
|
||||||
<span>{name}</span>
|
<Typography style={ICON_STYLE.PLUS}>{name}</Typography>
|
||||||
{isLoading ? (
|
|
||||||
<Spin spinning size="small" indicator={<LoadingOutlined spin />} />
|
{renderContent}
|
||||||
) : (
|
|
||||||
isHovered &&
|
|
||||||
buttonOnClick && (
|
|
||||||
<Popover content={<span>{iconHoverText}</span>}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
icon={buttonIcon}
|
|
||||||
onClick={(): void => buttonOnClick({ fieldData, fieldIndex })}
|
|
||||||
style={{ color: 'inherit', padding: 0, height: '1rem', width: '1rem' }}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FieldItemProps {
|
||||||
|
name: string;
|
||||||
|
buttonIcon: React.ReactNode;
|
||||||
|
buttonOnClick: (props: {
|
||||||
|
fieldData: IInterestingFields | ISelectedFields;
|
||||||
|
fieldIndex: number;
|
||||||
|
}) => void;
|
||||||
|
fieldData: IField;
|
||||||
|
fieldIndex: number;
|
||||||
|
isLoading: boolean;
|
||||||
|
iconHoverText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FieldItem;
|
||||||
|
8
frontend/src/container/LogsFilters/config.ts
Normal file
8
frontend/src/container/LogsFilters/config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { blue, red } from '@ant-design/colors';
|
||||||
|
|
||||||
|
export const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
||||||
|
|
||||||
|
export const ICON_STYLE = {
|
||||||
|
PLUS: { color: blue[5] },
|
||||||
|
CLOSE: { color: red[5] },
|
||||||
|
};
|
@ -1,27 +1,19 @@
|
|||||||
/* eslint-disable react/no-array-index-key */
|
|
||||||
import { red } from '@ant-design/colors';
|
|
||||||
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
|
import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
|
||||||
import { Input } from 'antd';
|
import { Input } from 'antd';
|
||||||
import AddToSelectedFields from 'api/logs/AddToSelectedField';
|
|
||||||
import RemoveSelectedField from 'api/logs/RemoveFromSelectedField';
|
|
||||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||||
import React, { memo, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { GetLogsFields } from 'store/actions/logs/getFields';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { IInterestingFields, ISelectedFields } from 'types/api/logs/fields';
|
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import { FieldItem } from './FieldItem';
|
import { ICON_STYLE } from './config';
|
||||||
|
import FieldItem from './FieldItem';
|
||||||
import { CategoryContainer, Container, FieldContainer } from './styles';
|
import { CategoryContainer, Container, FieldContainer } from './styles';
|
||||||
|
import { IHandleInterestProps, IHandleRemoveInterestProps } from './types';
|
||||||
|
import { onHandleAddInterest, onHandleRemoveInterest } from './utils';
|
||||||
|
|
||||||
const RESTRICTED_SELECTED_FIELDS = ['timestamp', 'id'];
|
function LogsFilters(): JSX.Element {
|
||||||
|
|
||||||
function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
|
||||||
const {
|
const {
|
||||||
fields: { interesting, selected },
|
fields: { interesting, selected },
|
||||||
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
} = useSelector<AppState, ILogsReducer>((state) => state.logs);
|
||||||
@ -36,57 +28,40 @@ function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
|||||||
setFilterValuesInput((e.target as HTMLInputElement).value);
|
setFilterValuesInput((e.target as HTMLInputElement).value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddInterestingToSelected = async ({
|
const onHandleAddSelectedToInteresting = useCallback(
|
||||||
fieldData,
|
({ fieldData, fieldIndex }: IHandleInterestProps) => (): Promise<void> =>
|
||||||
fieldIndex,
|
onHandleAddInterest({
|
||||||
}: {
|
fieldData,
|
||||||
fieldData: IInterestingFields;
|
fieldIndex,
|
||||||
fieldIndex: number;
|
interesting,
|
||||||
}): Promise<void> => {
|
interestingFieldLoading,
|
||||||
setInterestingFieldLoading((prevState: number[]) => {
|
setInterestingFieldLoading,
|
||||||
prevState.push(fieldIndex);
|
selected,
|
||||||
return [...prevState];
|
}),
|
||||||
});
|
[interesting, interestingFieldLoading, selected],
|
||||||
|
);
|
||||||
|
|
||||||
await AddToSelectedFields({
|
const onHandleRemoveSelected = useCallback(
|
||||||
...fieldData,
|
({
|
||||||
selected: true,
|
fieldData,
|
||||||
});
|
fieldIndex,
|
||||||
getLogsFields();
|
}: IHandleRemoveInterestProps) => (): Promise<void> =>
|
||||||
|
onHandleRemoveInterest({
|
||||||
|
fieldData,
|
||||||
|
fieldIndex,
|
||||||
|
interesting,
|
||||||
|
interestingFieldLoading,
|
||||||
|
selected,
|
||||||
|
setSelectedFieldLoading,
|
||||||
|
}),
|
||||||
|
[interesting, interestingFieldLoading, selected, setSelectedFieldLoading],
|
||||||
|
);
|
||||||
|
|
||||||
setInterestingFieldLoading(
|
|
||||||
interestingFieldLoading.filter((e) => e !== fieldIndex),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const handleRemoveSelectedField = async ({
|
|
||||||
fieldData,
|
|
||||||
fieldIndex,
|
|
||||||
}: {
|
|
||||||
fieldData: ISelectedFields;
|
|
||||||
fieldIndex: number;
|
|
||||||
}): Promise<void> => {
|
|
||||||
setSelectedFieldLoading((prevState) => {
|
|
||||||
prevState.push(fieldIndex);
|
|
||||||
return [...prevState];
|
|
||||||
});
|
|
||||||
|
|
||||||
await RemoveSelectedField({
|
|
||||||
...fieldData,
|
|
||||||
selected: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
getLogsFields();
|
|
||||||
|
|
||||||
setSelectedFieldLoading(
|
|
||||||
interestingFieldLoading.filter((e) => e !== fieldIndex),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Container flex="450px">
|
<Container flex="450px">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Filter Values"
|
placeholder="Filter Values"
|
||||||
onInput={handleSearch}
|
onInput={handleSearch}
|
||||||
style={{ width: '100%' }}
|
|
||||||
value={filterValuesInput}
|
value={filterValuesInput}
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
/>
|
/>
|
||||||
@ -98,15 +73,15 @@ function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
|||||||
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
||||||
.map((field, idx) => (
|
.map((field, idx) => (
|
||||||
<FieldItem
|
<FieldItem
|
||||||
key={`${JSON.stringify(field)}-${idx}`}
|
key={`${JSON.stringify(field)}`}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
fieldData={field as never}
|
fieldData={field}
|
||||||
fieldIndex={idx}
|
fieldIndex={idx}
|
||||||
buttonIcon={<CloseOutlined style={{ color: red[5] }} />}
|
buttonIcon={<CloseOutlined style={ICON_STYLE.CLOSE} />}
|
||||||
buttonOnClick={
|
buttonOnClick={onHandleRemoveSelected({
|
||||||
(!RESTRICTED_SELECTED_FIELDS.includes(field.name) &&
|
fieldData: field,
|
||||||
handleRemoveSelectedField) as never
|
fieldIndex: idx,
|
||||||
}
|
})}
|
||||||
isLoading={selectedFieldLoading.includes(idx)}
|
isLoading={selectedFieldLoading.includes(idx)}
|
||||||
iconHoverText="Remove from Selected Fields"
|
iconHoverText="Remove from Selected Fields"
|
||||||
/>
|
/>
|
||||||
@ -120,33 +95,23 @@ function LogsFilters({ getLogsFields }: LogsFiltersProps): JSX.Element {
|
|||||||
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
.filter((field) => fieldSearchFilter(field.name, filterValuesInput))
|
||||||
.map((field, idx) => (
|
.map((field, idx) => (
|
||||||
<FieldItem
|
<FieldItem
|
||||||
key={`${JSON.stringify(field)}-${idx}`}
|
key={`${JSON.stringify(field)}`}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
fieldData={field as never}
|
fieldData={field}
|
||||||
fieldIndex={idx}
|
fieldIndex={idx}
|
||||||
buttonIcon={<PlusCircleFilled />}
|
buttonIcon={<PlusCircleFilled style={ICON_STYLE.PLUS} />}
|
||||||
buttonOnClick={handleAddInterestingToSelected as never}
|
buttonOnClick={onHandleAddSelectedToInteresting({
|
||||||
|
fieldData: field,
|
||||||
|
fieldIndex: idx,
|
||||||
|
})}
|
||||||
isLoading={interestingFieldLoading.includes(idx)}
|
isLoading={interestingFieldLoading.includes(idx)}
|
||||||
iconHoverText="Add to Selected Fields"
|
iconHoverText="Add to Selected Fields"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</FieldContainer>
|
</FieldContainer>
|
||||||
</CategoryContainer>
|
</CategoryContainer>
|
||||||
{/* <ExtractField>Extract Fields</ExtractField> */}
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
export default LogsFilters;
|
||||||
getLogsFields: () => (dispatch: Dispatch<AppActions>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
type LogsFiltersProps = DispatchProps;
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(memo(LogsFilters));
|
|
||||||
|
@ -4,8 +4,8 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
export const Container = styled(Col)`
|
export const Container = styled(Col)`
|
||||||
padding-top: 0.3rem;
|
padding-top: 0.3rem;
|
||||||
min-width: 250px;
|
min-width: 15.625rem;
|
||||||
max-width: 350px;
|
max-width: 21.875rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CategoryContainer = styled.div`
|
export const CategoryContainer = styled.div`
|
||||||
@ -21,6 +21,7 @@ export const FieldContainer = styled(Typography.Text)`
|
|||||||
export const Field = styled.div<{ isDarkMode: boolean }>`
|
export const Field = styled.div<{ isDarkMode: boolean }>`
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
|
height: 2rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
35
frontend/src/container/LogsFilters/types.ts
Normal file
35
frontend/src/container/LogsFilters/types.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
IField,
|
||||||
|
IInterestingFields,
|
||||||
|
ISelectedFields,
|
||||||
|
} from 'types/api/logs/fields';
|
||||||
|
|
||||||
|
type SetLoading = (value: React.SetStateAction<number[]>) => void;
|
||||||
|
|
||||||
|
export type IHandleInterestProps = {
|
||||||
|
fieldData: IInterestingFields;
|
||||||
|
fieldIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IHandleRemoveInterestProps = {
|
||||||
|
fieldData: ISelectedFields;
|
||||||
|
fieldIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface OnHandleAddInterestProps {
|
||||||
|
setInterestingFieldLoading: SetLoading;
|
||||||
|
fieldIndex: number;
|
||||||
|
fieldData: ISelectedFields;
|
||||||
|
interesting: IField[];
|
||||||
|
interestingFieldLoading: number[];
|
||||||
|
selected: IField[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OnHandleRemoveInterestProps {
|
||||||
|
setSelectedFieldLoading: SetLoading;
|
||||||
|
selected: IField[];
|
||||||
|
interesting: IField[];
|
||||||
|
interestingFieldLoading: number[];
|
||||||
|
fieldData: IInterestingFields;
|
||||||
|
fieldIndex: number;
|
||||||
|
}
|
94
frontend/src/container/LogsFilters/utils.ts
Normal file
94
frontend/src/container/LogsFilters/utils.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import AddToSelectedFields from 'api/logs/AddToSelectedField';
|
||||||
|
import RemoveSelectedField from 'api/logs/RemoveFromSelectedField';
|
||||||
|
import store from 'store';
|
||||||
|
import {
|
||||||
|
UPDATE_INTERESTING_FIELDS,
|
||||||
|
UPDATE_SELECTED_FIELDS,
|
||||||
|
} from 'types/actions/logs';
|
||||||
|
|
||||||
|
import { RESTRICTED_SELECTED_FIELDS } from './config';
|
||||||
|
import { OnHandleAddInterestProps, OnHandleRemoveInterestProps } from './types';
|
||||||
|
|
||||||
|
export const onHandleAddInterest = async ({
|
||||||
|
setInterestingFieldLoading,
|
||||||
|
fieldIndex,
|
||||||
|
fieldData,
|
||||||
|
interesting,
|
||||||
|
interestingFieldLoading,
|
||||||
|
selected,
|
||||||
|
}: OnHandleAddInterestProps): Promise<void> => {
|
||||||
|
const { dispatch } = store;
|
||||||
|
|
||||||
|
setInterestingFieldLoading((prevState: number[]) => {
|
||||||
|
prevState.push(fieldIndex);
|
||||||
|
return [...prevState];
|
||||||
|
});
|
||||||
|
|
||||||
|
await AddToSelectedFields({
|
||||||
|
...fieldData,
|
||||||
|
selected: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_INTERESTING_FIELDS,
|
||||||
|
payload: {
|
||||||
|
field: interesting.filter((e) => e.name !== fieldData.name),
|
||||||
|
type: 'selected',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_SELECTED_FIELDS,
|
||||||
|
payload: {
|
||||||
|
field: [...selected, fieldData],
|
||||||
|
type: 'selected',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterestingFieldLoading(
|
||||||
|
interestingFieldLoading.filter((e) => e !== fieldIndex),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const onHandleRemoveInterest = async ({
|
||||||
|
setSelectedFieldLoading,
|
||||||
|
selected,
|
||||||
|
interesting,
|
||||||
|
interestingFieldLoading,
|
||||||
|
fieldData,
|
||||||
|
fieldIndex,
|
||||||
|
}: OnHandleRemoveInterestProps): Promise<void> => {
|
||||||
|
if (RESTRICTED_SELECTED_FIELDS.includes(fieldData.name)) return;
|
||||||
|
|
||||||
|
const { dispatch } = store;
|
||||||
|
|
||||||
|
setSelectedFieldLoading((prevState) => {
|
||||||
|
prevState.push(fieldIndex);
|
||||||
|
return [...prevState];
|
||||||
|
});
|
||||||
|
|
||||||
|
await RemoveSelectedField({
|
||||||
|
...fieldData,
|
||||||
|
selected: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_SELECTED_FIELDS,
|
||||||
|
payload: {
|
||||||
|
field: selected.filter((e) => e.name !== fieldData.name),
|
||||||
|
type: 'selected',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_INTERESTING_FIELDS,
|
||||||
|
payload: {
|
||||||
|
field: [...interesting, fieldData],
|
||||||
|
type: 'interesting',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setSelectedFieldLoading(
|
||||||
|
interestingFieldLoading.filter((e) => e !== fieldIndex),
|
||||||
|
);
|
||||||
|
};
|
@ -1,9 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable no-bitwise */
|
|
||||||
/* eslint-disable sonarjs/no-identical-functions */
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
/* eslint-disable react/no-array-index-key */
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { CloseOutlined, CloseSquareOutlined } from '@ant-design/icons';
|
import { CloseOutlined, CloseSquareOutlined } from '@ant-design/icons';
|
||||||
import { Button, Input, Select } from 'antd';
|
import { Button, Input, Select } from 'antd';
|
||||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||||
@ -12,7 +6,7 @@ import {
|
|||||||
QueryOperatorsMultiVal,
|
QueryOperatorsMultiVal,
|
||||||
QueryOperatorsSingleVal,
|
QueryOperatorsSingleVal,
|
||||||
} from 'lib/logql/tokens';
|
} from 'lib/logql/tokens';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
@ -52,7 +46,7 @@ function QueryConditionField({
|
|||||||
interface QueryFieldProps {
|
interface QueryFieldProps {
|
||||||
query: Query;
|
query: Query;
|
||||||
queryIndex: number;
|
queryIndex: number;
|
||||||
onUpdate: (query: unknown, queryIndex: number) => void;
|
onUpdate: (query: Query, queryIndex: number) => void;
|
||||||
onDelete: (queryIndex: number) => void;
|
onDelete: (queryIndex: number) => void;
|
||||||
}
|
}
|
||||||
function QueryField({
|
function QueryField({
|
||||||
@ -64,34 +58,39 @@ function QueryField({
|
|||||||
const {
|
const {
|
||||||
fields: { selected },
|
fields: { selected },
|
||||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||||
const getFieldType = (inputKey: string): string => {
|
const getFieldType = useCallback(
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
(inputKey: string): string => {
|
||||||
for (const selectedField of selected) {
|
const selectedField = selected.find((field) => inputKey === field.name);
|
||||||
if (inputKey === selectedField.name) {
|
if (selectedField) {
|
||||||
return selectedField.type;
|
return selectedField.type;
|
||||||
}
|
}
|
||||||
}
|
return '';
|
||||||
return '';
|
},
|
||||||
};
|
[selected],
|
||||||
|
);
|
||||||
|
|
||||||
const fieldType = useMemo(() => getFieldType(query[0].value as string), [
|
const fieldType = useMemo(() => getFieldType(query[0].value as string), [
|
||||||
|
getFieldType,
|
||||||
query,
|
query,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleChange = (qIdx: number, value: string): void => {
|
const handleChange = (qIdx: number, value: string): void => {
|
||||||
query[qIdx].value = value || '';
|
const updatedQuery = [...query];
|
||||||
|
updatedQuery[qIdx].value = value || '';
|
||||||
|
|
||||||
if (qIdx === 1) {
|
if (qIdx === 1) {
|
||||||
if (Object.values(QueryOperatorsMultiVal).includes(value)) {
|
if (Object.values(QueryOperatorsMultiVal).includes(value)) {
|
||||||
if (!Array.isArray(query[2].value)) {
|
if (!Array.isArray(updatedQuery[2].value)) {
|
||||||
query[2].value = [];
|
updatedQuery[2].value = [];
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
Object.values(QueryOperatorsSingleVal).includes(value) &&
|
Object.values(QueryOperatorsSingleVal).includes(value) &&
|
||||||
Array.isArray(query[2].value)
|
Array.isArray(updatedQuery[2].value)
|
||||||
) {
|
) {
|
||||||
query[2].value = '';
|
updatedQuery[2].value = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onUpdate(query, queryIndex);
|
onUpdate(updatedQuery, queryIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClear = (): void => {
|
const handleClear = (): void => {
|
||||||
@ -210,16 +209,16 @@ function QueryBuilder({
|
|||||||
if (Array.isArray(query) && query.length > 1) {
|
if (Array.isArray(query) && query.length > 1) {
|
||||||
result.push(
|
result.push(
|
||||||
<QueryField
|
<QueryField
|
||||||
key={keyPrefix + idx}
|
key={keyPrefix}
|
||||||
query={query as never}
|
query={query}
|
||||||
queryIndex={idx}
|
queryIndex={idx}
|
||||||
onUpdate={handleUpdate as never}
|
onUpdate={handleUpdate}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result.push(
|
result.push(
|
||||||
<div key={keyPrefix + idx}>
|
<div key={keyPrefix}>
|
||||||
<QueryConditionField
|
<QueryConditionField
|
||||||
query={Array.isArray(query) ? query[0] : query}
|
query={Array.isArray(query) ? query[0] : query}
|
||||||
queryIndex={idx}
|
queryIndex={idx}
|
||||||
|
@ -36,6 +36,8 @@ function SearchFields({
|
|||||||
|
|
||||||
const keyPrefixRef = useRef(hashCode(JSON.stringify(fieldsQuery)));
|
const keyPrefixRef = useRef(hashCode(JSON.stringify(fieldsQuery)));
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updatedFieldsQuery = createParsedQueryStructure([
|
const updatedFieldsQuery = createParsedQueryStructure([
|
||||||
...parsedQuery,
|
...parsedQuery,
|
||||||
@ -81,7 +83,7 @@ function SearchFields({
|
|||||||
const flatParsedQuery = flatten(fieldsQuery);
|
const flatParsedQuery = flatten(fieldsQuery);
|
||||||
|
|
||||||
if (!fieldsQueryIsvalid(flatParsedQuery)) {
|
if (!fieldsQueryIsvalid(flatParsedQuery)) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Please enter a valid criteria for each of the selected fields',
|
message: 'Please enter a valid criteria for each of the selected fields',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -90,7 +92,7 @@ function SearchFields({
|
|||||||
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
|
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
|
||||||
updateParsedQuery(flatParsedQuery);
|
updateParsedQuery(flatParsedQuery);
|
||||||
onDropDownToggleHandler(false)();
|
onDropDownToggleHandler(false)();
|
||||||
}, [onDropDownToggleHandler, fieldsQuery, updateParsedQuery]);
|
}, [onDropDownToggleHandler, fieldsQuery, updateParsedQuery, notifications]);
|
||||||
|
|
||||||
const clearFilters = useCallback((): void => {
|
const clearFilters = useCallback((): void => {
|
||||||
keyPrefixRef.current = hashCode(JSON.stringify([]));
|
keyPrefixRef.current = hashCode(JSON.stringify([]));
|
||||||
@ -100,6 +102,7 @@ function SearchFields({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
<QueryBuilder
|
<QueryBuilder
|
||||||
key={keyPrefixRef.current}
|
key={keyPrefixRef.current}
|
||||||
keyPrefix={keyPrefixRef.current}
|
keyPrefix={keyPrefixRef.current}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Input, InputRef, Popover } from 'antd';
|
import { Input, InputRef, Popover } from 'antd';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import getStep from 'lib/getStep';
|
import getStep from 'lib/getStep';
|
||||||
import { debounce } from 'lodash-es';
|
import debounce from 'lodash-es/debounce';
|
||||||
import React, {
|
import React, {
|
||||||
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@ -12,6 +13,7 @@ import React, {
|
|||||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import { GetLogsFields } from 'store/actions/logs/getFields';
|
||||||
import { getLogs } from 'store/actions/logs/getLogs';
|
import { getLogs } from 'store/actions/logs/getLogs';
|
||||||
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
import { getLogsAggregate } from 'store/actions/logs/getLogsAggregate';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -32,6 +34,7 @@ import { useSearchParser } from './useSearchParser';
|
|||||||
function SearchFilter({
|
function SearchFilter({
|
||||||
getLogs,
|
getLogs,
|
||||||
getLogsAggregate,
|
getLogsAggregate,
|
||||||
|
getLogsFields,
|
||||||
}: SearchFilterProps): JSX.Element {
|
}: SearchFilterProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
updateParsedQuery,
|
updateParsedQuery,
|
||||||
@ -45,7 +48,7 @@ function SearchFilter({
|
|||||||
AppState,
|
AppState,
|
||||||
ILogsReducer
|
ILogsReducer
|
||||||
>((state) => state.logs);
|
>((state) => state.logs);
|
||||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
@ -69,6 +72,8 @@ function SearchFilter({
|
|||||||
|
|
||||||
const handleSearch = useCallback(
|
const handleSearch = useCallback(
|
||||||
(customQuery: string) => {
|
(customQuery: string) => {
|
||||||
|
getLogsFields();
|
||||||
|
|
||||||
if (liveTail === 'PLAYING') {
|
if (liveTail === 'PLAYING') {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: TOGGLE_LIVE_TAIL,
|
type: TOGGLE_LIVE_TAIL,
|
||||||
@ -77,15 +82,13 @@ function SearchFilter({
|
|||||||
dispatch({
|
dispatch({
|
||||||
type: FLUSH_LOGS,
|
type: FLUSH_LOGS,
|
||||||
});
|
});
|
||||||
setTimeout(
|
dispatch({
|
||||||
() =>
|
type: TOGGLE_LIVE_TAIL,
|
||||||
dispatch({
|
payload: liveTail,
|
||||||
type: TOGGLE_LIVE_TAIL,
|
});
|
||||||
payload: liveTail,
|
|
||||||
}),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
const { maxTime, minTime } = globalTime;
|
||||||
|
|
||||||
getLogs({
|
getLogs({
|
||||||
q: customQuery,
|
q: customQuery,
|
||||||
limit: logLinesPerPage,
|
limit: logLinesPerPage,
|
||||||
@ -117,8 +120,8 @@ function SearchFilter({
|
|||||||
idStart,
|
idStart,
|
||||||
liveTail,
|
liveTail,
|
||||||
logLinesPerPage,
|
logLinesPerPage,
|
||||||
maxTime,
|
globalTime,
|
||||||
minTime,
|
getLogsFields,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -145,12 +148,12 @@ function SearchFilter({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
urlQueryString,
|
urlQueryString,
|
||||||
maxTime,
|
|
||||||
minTime,
|
|
||||||
idEnd,
|
idEnd,
|
||||||
idStart,
|
idStart,
|
||||||
logLinesPerPage,
|
logLinesPerPage,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
globalTime.maxTime,
|
||||||
|
globalTime.minTime,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -194,6 +197,7 @@ function SearchFilter({
|
|||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
getLogs: typeof getLogs;
|
getLogs: typeof getLogs;
|
||||||
getLogsAggregate: typeof getLogsAggregate;
|
getLogsAggregate: typeof getLogsAggregate;
|
||||||
|
getLogsFields: typeof GetLogsFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchFilterProps = DispatchProps;
|
type SearchFilterProps = DispatchProps;
|
||||||
@ -203,6 +207,7 @@ const mapDispatchToProps = (
|
|||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
getLogs: bindActionCreators(getLogs, dispatch),
|
getLogs: bindActionCreators(getLogs, dispatch),
|
||||||
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
getLogsAggregate: bindActionCreators(getLogsAggregate, dispatch),
|
||||||
|
getLogsFields: bindActionCreators(GetLogsFields, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(SearchFilter);
|
export default connect(null, mapDispatchToProps)(memo(SearchFilter));
|
||||||
|
@ -1,38 +1,59 @@
|
|||||||
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { parseQuery, reverseParser } from 'lib/logql';
|
import { parseQuery, reverseParser } from 'lib/logql';
|
||||||
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
import { ILogQLParsedQueryItem } from 'lib/logql/types';
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
import {
|
import {
|
||||||
SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
SET_SEARCH_QUERY_PARSED_PAYLOAD,
|
||||||
SET_SEARCH_QUERY_STRING,
|
SET_SEARCH_QUERY_STRING,
|
||||||
} from 'types/actions/logs';
|
} from 'types/actions/logs';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
|
import { getGlobalTime } from './utils';
|
||||||
|
|
||||||
export function useSearchParser(): {
|
export function useSearchParser(): {
|
||||||
queryString: string;
|
queryString: string;
|
||||||
parsedQuery: unknown;
|
parsedQuery: unknown;
|
||||||
updateParsedQuery: (arg0: ILogQLParsedQueryItem[]) => void;
|
updateParsedQuery: (arg0: ILogQLParsedQueryItem[]) => void;
|
||||||
updateQueryString: (arg0: string) => void;
|
updateQueryString: (arg0: string) => void;
|
||||||
} {
|
} {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
const {
|
const {
|
||||||
searchFilter: { parsedQuery, queryString },
|
searchFilter: { parsedQuery, queryString },
|
||||||
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
|
||||||
|
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
const parsedFilters = useMemo(() => urlQuery.get('q'), [urlQuery]);
|
||||||
|
|
||||||
|
const { minTime, maxTime, selectedTime } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((store) => store.globalTime);
|
||||||
|
|
||||||
const updateQueryString = useCallback(
|
const updateQueryString = useCallback(
|
||||||
(updatedQueryString: string) => {
|
(updatedQueryString: string) => {
|
||||||
history.replace({
|
history.replace({
|
||||||
pathname: history.location.pathname,
|
pathname: history.location.pathname,
|
||||||
search: updatedQueryString ? `?q=${updatedQueryString}` : '',
|
search: `?q=${updatedQueryString}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const globalTime = getMinMax(selectedTime, minTime, maxTime);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_SEARCH_QUERY_STRING,
|
type: SET_SEARCH_QUERY_STRING,
|
||||||
payload: updatedQueryString,
|
payload: {
|
||||||
|
searchQueryString: updatedQueryString,
|
||||||
|
globalTime: getGlobalTime(selectedTime, globalTime),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsedQueryFromString = parseQuery(updatedQueryString);
|
const parsedQueryFromString = parseQuery(updatedQueryString);
|
||||||
if (!isEqual(parsedQuery, parsedQueryFromString)) {
|
if (!isEqual(parsedQuery, parsedQueryFromString)) {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -41,12 +62,18 @@ export function useSearchParser(): {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// need to hide this warning as we don't want to update the query string on every change
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[dispatch, parsedQuery],
|
[dispatch, parsedQuery],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryString !== null) updateQueryString(queryString);
|
if (!queryString && parsedFilters) {
|
||||||
}, [queryString, updateQueryString]);
|
updateQueryString(parsedFilters);
|
||||||
|
} else if (queryString) {
|
||||||
|
updateQueryString(queryString);
|
||||||
|
}
|
||||||
|
}, [queryString, updateQueryString, parsedFilters]);
|
||||||
|
|
||||||
const updateParsedQuery = useCallback(
|
const updateParsedQuery = useCallback(
|
||||||
(updatedParsedPayload: ILogQLParsedQueryItem[]) => {
|
(updatedParsedPayload: ILogQLParsedQueryItem[]) => {
|
||||||
@ -61,7 +88,9 @@ export function useSearchParser(): {
|
|||||||
) {
|
) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SET_SEARCH_QUERY_STRING,
|
type: SET_SEARCH_QUERY_STRING,
|
||||||
payload: reversedParsedQuery,
|
payload: {
|
||||||
|
searchQueryString: reversedParsedQuery,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
12
frontend/src/container/LogsSearchFilter/utils.ts
Normal file
12
frontend/src/container/LogsSearchFilter/utils.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||||
|
import { GetMinMaxPayload } from 'lib/getMinMax';
|
||||||
|
|
||||||
|
export const getGlobalTime = (
|
||||||
|
selectedTime: Time,
|
||||||
|
globalTime: GetMinMaxPayload,
|
||||||
|
): GetMinMaxPayload | undefined => {
|
||||||
|
if (selectedTime === 'custom') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return globalTime;
|
||||||
|
};
|
@ -47,7 +47,7 @@ export const databaseCallsAvgDuration = ({
|
|||||||
const metricNameA = 'signoz_db_latency_sum';
|
const metricNameA = 'signoz_db_latency_sum';
|
||||||
const metricNameB = 'signoz_db_latency_count';
|
const metricNameB = 'signoz_db_latency_count';
|
||||||
const expression = 'A/B';
|
const expression = 'A/B';
|
||||||
const legendFormula = '';
|
const legendFormula = 'Average Duration';
|
||||||
const legend = '';
|
const legend = '';
|
||||||
const disabled = true;
|
const disabled = true;
|
||||||
const additionalItemsA = [
|
const additionalItemsA = [
|
||||||
|
@ -4,8 +4,11 @@ import {
|
|||||||
databaseCallsAvgDuration,
|
databaseCallsAvgDuration,
|
||||||
databaseCallsRPS,
|
databaseCallsRPS,
|
||||||
} from 'container/MetricsApplication/MetricsPageQueries/DBCallQueries';
|
} from 'container/MetricsApplication/MetricsPageQueries/DBCallQueries';
|
||||||
import { resourceAttributesToTagFilterItems } from 'lib/resourceAttributes';
|
import {
|
||||||
import React, { useMemo } from 'react';
|
convertRawQueriesToTraceSelectedTags,
|
||||||
|
resourceAttributesToTagFilterItems,
|
||||||
|
} from 'lib/resourceAttributes';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -13,9 +16,16 @@ import { Widgets } from 'types/api/dashboard/getAll';
|
|||||||
import MetricReducer from 'types/reducer/metrics';
|
import MetricReducer from 'types/reducer/metrics';
|
||||||
|
|
||||||
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
||||||
|
import { Button } from './styles';
|
||||||
|
import {
|
||||||
|
dbSystemTags,
|
||||||
|
onGraphClickHandler,
|
||||||
|
onViewTracePopupClick,
|
||||||
|
} from './util';
|
||||||
|
|
||||||
function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
||||||
const { servicename } = useParams<{ servicename?: string }>();
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
|
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||||
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
const { resourceAttributeQueries } = useSelector<AppState, MetricReducer>(
|
||||||
(state) => state.metrics,
|
(state) => state.metrics,
|
||||||
);
|
);
|
||||||
@ -23,6 +33,15 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
|||||||
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
|
() => resourceAttributesToTagFilterItems(resourceAttributeQueries) || [],
|
||||||
[resourceAttributeQueries],
|
[resourceAttributeQueries],
|
||||||
);
|
);
|
||||||
|
const selectedTraceTags: string = useMemo(
|
||||||
|
() =>
|
||||||
|
JSON.stringify(
|
||||||
|
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries).concat(
|
||||||
|
...dbSystemTags,
|
||||||
|
) || [],
|
||||||
|
),
|
||||||
|
[resourceAttributeQueries],
|
||||||
|
);
|
||||||
const legend = '{{db_system}}';
|
const legend = '{{db_system}}';
|
||||||
|
|
||||||
const databaseCallsRPSWidget = useMemo(
|
const databaseCallsRPSWidget = useMemo(
|
||||||
@ -39,7 +58,6 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
|||||||
}),
|
}),
|
||||||
[getWidgetQueryBuilder, servicename, tagFilterItems],
|
[getWidgetQueryBuilder, servicename, tagFilterItems],
|
||||||
);
|
);
|
||||||
|
|
||||||
const databaseCallsAverageDurationWidget = useMemo(
|
const databaseCallsAverageDurationWidget = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getWidgetQueryBuilder({
|
getWidgetQueryBuilder({
|
||||||
@ -57,6 +75,18 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
id="database_call_rps_button"
|
||||||
|
onClick={onViewTracePopupClick(
|
||||||
|
servicename,
|
||||||
|
selectedTraceTags,
|
||||||
|
selectedTimeStamp,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</Button>
|
||||||
<Card>
|
<Card>
|
||||||
<GraphTitle>Database Calls RPS</GraphTitle>
|
<GraphTitle>Database Calls RPS</GraphTitle>
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
@ -65,12 +95,33 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
|||||||
fullViewOptions={false}
|
fullViewOptions={false}
|
||||||
widget={databaseCallsRPSWidget}
|
widget={databaseCallsRPSWidget}
|
||||||
yAxisUnit="reqps"
|
yAxisUnit="reqps"
|
||||||
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||||
|
onGraphClickHandler(setSelectedTimeStamp)(
|
||||||
|
ChartEvent,
|
||||||
|
activeElements,
|
||||||
|
chart,
|
||||||
|
data,
|
||||||
|
'database_call_rps',
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
id="database_call_avg_duration_button"
|
||||||
|
onClick={onViewTracePopupClick(
|
||||||
|
servicename,
|
||||||
|
selectedTraceTags,
|
||||||
|
selectedTimeStamp,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</Button>
|
||||||
<Card>
|
<Card>
|
||||||
<GraphTitle>Database Calls Avg Duration</GraphTitle>
|
<GraphTitle>Database Calls Avg Duration</GraphTitle>
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
@ -79,6 +130,15 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
|
|||||||
fullViewOptions={false}
|
fullViewOptions={false}
|
||||||
widget={databaseCallsAverageDurationWidget}
|
widget={databaseCallsAverageDurationWidget}
|
||||||
yAxisUnit="ms"
|
yAxisUnit="ms"
|
||||||
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||||
|
onGraphClickHandler(setSelectedTimeStamp)(
|
||||||
|
ChartEvent,
|
||||||
|
activeElements,
|
||||||
|
chart,
|
||||||
|
data,
|
||||||
|
'database_call_avg_duration',
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
|
||||||
import Graph from 'components/Graph';
|
import Graph from 'components/Graph';
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
@ -10,7 +9,7 @@ import {
|
|||||||
convertRawQueriesToTraceSelectedTags,
|
convertRawQueriesToTraceSelectedTags,
|
||||||
resourceAttributesToTagFilterItems,
|
resourceAttributesToTagFilterItems,
|
||||||
} from 'lib/resourceAttributes';
|
} from 'lib/resourceAttributes';
|
||||||
import React, { useCallback, useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { UpdateTimeInterval } from 'store/actions';
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
@ -25,10 +24,11 @@ import {
|
|||||||
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
||||||
import TopOperationsTable from '../TopOperationsTable';
|
import TopOperationsTable from '../TopOperationsTable';
|
||||||
import { Button } from './styles';
|
import { Button } from './styles';
|
||||||
|
import { onGraphClickHandler, onViewTracePopupClick } from './util';
|
||||||
|
|
||||||
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
||||||
const { servicename } = useParams<{ servicename?: string }>();
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
const selectedTimeStamp = useRef(0);
|
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -39,7 +39,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
|
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
|
||||||
|
|
||||||
const selectedTraceTags: string = JSON.stringify(
|
const selectedTraceTags: string = JSON.stringify(
|
||||||
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries, 'array') || [],
|
convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [],
|
||||||
);
|
);
|
||||||
|
|
||||||
const tagFilterItems = useMemo(
|
const tagFilterItems = useMemo(
|
||||||
@ -77,58 +77,6 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
[servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder],
|
[servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onTracePopupClick = (timestamp: number): void => {
|
|
||||||
const currentTime = timestamp;
|
|
||||||
const tPlusOne = timestamp + 1 * 60 * 1000;
|
|
||||||
|
|
||||||
const urlParams = new URLSearchParams();
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
|
||||||
|
|
||||||
history.replace(
|
|
||||||
`${
|
|
||||||
ROUTES.TRACE
|
|
||||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickHandler = async (
|
|
||||||
event: ChartEvent,
|
|
||||||
elements: ActiveElement[],
|
|
||||||
chart: Chart,
|
|
||||||
data: ChartData,
|
|
||||||
from: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
if (event.native) {
|
|
||||||
const points = chart.getElementsAtEventForMode(
|
|
||||||
event.native,
|
|
||||||
'nearest',
|
|
||||||
{ intersect: true },
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const id = `${from}_button`;
|
|
||||||
const buttonElement = document.getElementById(id);
|
|
||||||
|
|
||||||
if (points.length !== 0) {
|
|
||||||
const firstPoint = points[0];
|
|
||||||
|
|
||||||
if (data.labels) {
|
|
||||||
const time = data?.labels[firstPoint.index] as Date;
|
|
||||||
|
|
||||||
if (buttonElement) {
|
|
||||||
buttonElement.style.display = 'block';
|
|
||||||
buttonElement.style.left = `${firstPoint.element.x}px`;
|
|
||||||
buttonElement.style.top = `${firstPoint.element.y}px`;
|
|
||||||
selectedTimeStamp.current = time.getTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (buttonElement && buttonElement.style.display === 'block') {
|
|
||||||
buttonElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDragSelect = useCallback(
|
const onDragSelect = useCallback(
|
||||||
(start: number, end: number) => {
|
(start: number, end: number) => {
|
||||||
const startTimestamp = Math.trunc(start);
|
const startTimestamp = Math.trunc(start);
|
||||||
@ -162,9 +110,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
type="default"
|
type="default"
|
||||||
size="small"
|
size="small"
|
||||||
id="Service_button"
|
id="Service_button"
|
||||||
onClick={(): void => {
|
onClick={onViewTracePopupClick(
|
||||||
onTracePopupClick(selectedTimeStamp.current);
|
servicename,
|
||||||
}}
|
selectedTraceTags,
|
||||||
|
selectedTimeStamp,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
View Traces
|
View Traces
|
||||||
</Button>
|
</Button>
|
||||||
@ -173,7 +123,13 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<Graph
|
<Graph
|
||||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||||
onClickHandler(ChartEvent, activeElements, chart, data, 'Service');
|
onGraphClickHandler(setSelectedTimeStamp)(
|
||||||
|
ChartEvent,
|
||||||
|
activeElements,
|
||||||
|
chart,
|
||||||
|
data,
|
||||||
|
'Service',
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
name="service_latency"
|
name="service_latency"
|
||||||
type="line"
|
type="line"
|
||||||
@ -230,9 +186,11 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
type="default"
|
type="default"
|
||||||
size="small"
|
size="small"
|
||||||
id="Rate_button"
|
id="Rate_button"
|
||||||
onClick={(): void => {
|
onClick={onViewTracePopupClick(
|
||||||
onTracePopupClick(selectedTimeStamp.current);
|
servicename,
|
||||||
}}
|
selectedTraceTags,
|
||||||
|
selectedTimeStamp,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
View Traces
|
View Traces
|
||||||
</Button>
|
</Button>
|
||||||
@ -243,7 +201,13 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
name="operations_per_sec"
|
name="operations_per_sec"
|
||||||
fullViewOptions={false}
|
fullViewOptions={false}
|
||||||
onClickHandler={(event, element, chart, data): void => {
|
onClickHandler={(event, element, chart, data): void => {
|
||||||
onClickHandler(event, element, chart, data, 'Rate');
|
onGraphClickHandler(setSelectedTimeStamp)(
|
||||||
|
event,
|
||||||
|
element,
|
||||||
|
chart,
|
||||||
|
data,
|
||||||
|
'Rate',
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
widget={operationPerSecWidget}
|
widget={operationPerSecWidget}
|
||||||
yAxisUnit="ops"
|
yAxisUnit="ops"
|
||||||
@ -260,7 +224,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
size="small"
|
size="small"
|
||||||
id="Error_button"
|
id="Error_button"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
onErrorTrackHandler(selectedTimeStamp.current);
|
onErrorTrackHandler(selectedTimeStamp);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
View Traces
|
View Traces
|
||||||
@ -273,7 +237,13 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
|
|||||||
name="error_percentage_%"
|
name="error_percentage_%"
|
||||||
fullViewOptions={false}
|
fullViewOptions={false}
|
||||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||||
onClickHandler(ChartEvent, activeElements, chart, data, 'Error');
|
onGraphClickHandler(setSelectedTimeStamp)(
|
||||||
|
ChartEvent,
|
||||||
|
activeElements,
|
||||||
|
chart,
|
||||||
|
data,
|
||||||
|
'Error',
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
widget={errorPercentageWidget}
|
widget={errorPercentageWidget}
|
||||||
yAxisUnit="%"
|
yAxisUnit="%"
|
||||||
|
75
frontend/src/container/MetricsApplication/Tabs/util.ts
Normal file
75
frontend/src/container/MetricsApplication/Tabs/util.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
||||||
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import history from 'lib/history';
|
||||||
|
import { Tags } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
export const dbSystemTags: Tags[] = [
|
||||||
|
{
|
||||||
|
Key: ['db.system.(string)'],
|
||||||
|
StringValues: [''],
|
||||||
|
NumberValues: [],
|
||||||
|
BoolValues: [],
|
||||||
|
Operator: 'Exists',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function onViewTracePopupClick(
|
||||||
|
servicename: string | undefined,
|
||||||
|
selectedTraceTags: string,
|
||||||
|
timestamp: number,
|
||||||
|
): VoidFunction {
|
||||||
|
return (): void => {
|
||||||
|
const currentTime = timestamp;
|
||||||
|
const tPlusOne = timestamp + 1 * 60 * 1000;
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams();
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
||||||
|
|
||||||
|
history.replace(
|
||||||
|
`${
|
||||||
|
ROUTES.TRACE
|
||||||
|
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onGraphClickHandler(
|
||||||
|
setSelectedTimeStamp: React.Dispatch<React.SetStateAction<number>>,
|
||||||
|
) {
|
||||||
|
return async (
|
||||||
|
event: ChartEvent,
|
||||||
|
elements: ActiveElement[],
|
||||||
|
chart: Chart,
|
||||||
|
data: ChartData,
|
||||||
|
from: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
if (event.native) {
|
||||||
|
const points = chart.getElementsAtEventForMode(
|
||||||
|
event.native,
|
||||||
|
'nearest',
|
||||||
|
{ intersect: true },
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
const id = `${from}_button`;
|
||||||
|
const buttonElement = document.getElementById(id);
|
||||||
|
|
||||||
|
if (points.length !== 0) {
|
||||||
|
const firstPoint = points[0];
|
||||||
|
|
||||||
|
if (data.labels) {
|
||||||
|
const time = data?.labels[firstPoint.index] as Date;
|
||||||
|
if (buttonElement) {
|
||||||
|
buttonElement.style.display = 'block';
|
||||||
|
buttonElement.style.left = `${firstPoint.element.x}px`;
|
||||||
|
buttonElement.style.top = `${firstPoint.element.y}px`;
|
||||||
|
setSelectedTimeStamp(time.getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (buttonElement && buttonElement.style.display === 'block') {
|
||||||
|
buttonElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { Table, Tooltip, Typography } from 'antd';
|
import { Tooltip, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -51,7 +52,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
|
|||||||
title: 'Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
ellipsis: true,
|
width: 100,
|
||||||
render: (text: string): JSX.Element => (
|
render: (text: string): JSX.Element => (
|
||||||
<Tooltip placement="topLeft" title={text}>
|
<Tooltip placement="topLeft" title={text}>
|
||||||
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
||||||
@ -64,6 +65,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
|
|||||||
title: 'P50 (in ms)',
|
title: 'P50 (in ms)',
|
||||||
dataIndex: 'p50',
|
dataIndex: 'p50',
|
||||||
key: 'p50',
|
key: 'p50',
|
||||||
|
width: 50,
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.p50 - b.p50,
|
sorter: (a: DataProps, b: DataProps): number => a.p50 - b.p50,
|
||||||
render: (value: number): string => (value / 1000000).toFixed(2),
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
},
|
},
|
||||||
@ -71,6 +73,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
|
|||||||
title: 'P95 (in ms)',
|
title: 'P95 (in ms)',
|
||||||
dataIndex: 'p95',
|
dataIndex: 'p95',
|
||||||
key: 'p95',
|
key: 'p95',
|
||||||
|
width: 50,
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.p95 - b.p95,
|
sorter: (a: DataProps, b: DataProps): number => a.p95 - b.p95,
|
||||||
render: (value: number): string => (value / 1000000).toFixed(2),
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
},
|
},
|
||||||
@ -78,6 +81,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
|
|||||||
title: 'P99 (in ms)',
|
title: 'P99 (in ms)',
|
||||||
dataIndex: 'p99',
|
dataIndex: 'p99',
|
||||||
key: 'p99',
|
key: 'p99',
|
||||||
|
width: 50,
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
|
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
|
||||||
render: (value: number): string => (value / 1000000).toFixed(2),
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
},
|
},
|
||||||
@ -85,18 +89,19 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
|
|||||||
title: 'Number of Calls',
|
title: 'Number of Calls',
|
||||||
dataIndex: 'numCalls',
|
dataIndex: 'numCalls',
|
||||||
key: 'numCalls',
|
key: 'numCalls',
|
||||||
|
width: 50,
|
||||||
sorter: (a: TopOperationListItem, b: TopOperationListItem): number =>
|
sorter: (a: TopOperationListItem, b: TopOperationListItem): number =>
|
||||||
a.numCalls - b.numCalls,
|
a.numCalls - b.numCalls,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
showHeader
|
showHeader
|
||||||
title={(): string => 'Key Operations'}
|
title={(): string => 'Key Operations'}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns}
|
|
||||||
rowKey="name"
|
rowKey="name"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { blue } from '@ant-design/colors';
|
import { blue } from '@ant-design/colors';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { Button, Card, Input, Space, Table } from 'antd';
|
import { Button, Card, Input, Space } from 'antd';
|
||||||
import type { ColumnsType, ColumnType } from 'antd/es/table';
|
import type { ColumnsType, ColumnType } from 'antd/es/table';
|
||||||
import type {
|
import type {
|
||||||
FilterConfirmProps,
|
FilterConfirmProps,
|
||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
} from 'antd/es/table/interface';
|
} from 'antd/es/table/interface';
|
||||||
import localStorageGet from 'api/browser/localstorage/get';
|
import localStorageGet from 'api/browser/localstorage/get';
|
||||||
import localStorageSet from 'api/browser/localstorage/set';
|
import localStorageSet from 'api/browser/localstorage/set';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
@ -101,6 +102,7 @@ function Metrics(): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Application',
|
title: 'Application',
|
||||||
dataIndex: 'serviceName',
|
dataIndex: 'serviceName',
|
||||||
|
width: 200,
|
||||||
key: 'serviceName',
|
key: 'serviceName',
|
||||||
...getColumnSearchProps('serviceName'),
|
...getColumnSearchProps('serviceName'),
|
||||||
},
|
},
|
||||||
@ -108,6 +110,7 @@ function Metrics(): JSX.Element {
|
|||||||
title: 'P99 latency (in ms)',
|
title: 'P99 latency (in ms)',
|
||||||
dataIndex: 'p99',
|
dataIndex: 'p99',
|
||||||
key: 'p99',
|
key: 'p99',
|
||||||
|
width: 150,
|
||||||
defaultSortOrder: 'descend',
|
defaultSortOrder: 'descend',
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
|
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
|
||||||
render: (value: number): string => (value / 1000000).toFixed(2),
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
@ -116,6 +119,7 @@ function Metrics(): JSX.Element {
|
|||||||
title: 'Error Rate (% of total)',
|
title: 'Error Rate (% of total)',
|
||||||
dataIndex: 'errorRate',
|
dataIndex: 'errorRate',
|
||||||
key: 'errorRate',
|
key: 'errorRate',
|
||||||
|
width: 150,
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.errorRate - b.errorRate,
|
sorter: (a: DataProps, b: DataProps): number => a.errorRate - b.errorRate,
|
||||||
render: (value: number): string => value.toFixed(2),
|
render: (value: number): string => value.toFixed(2),
|
||||||
},
|
},
|
||||||
@ -123,6 +127,7 @@ function Metrics(): JSX.Element {
|
|||||||
title: 'Operations Per Second',
|
title: 'Operations Per Second',
|
||||||
dataIndex: 'callRate',
|
dataIndex: 'callRate',
|
||||||
key: 'callRate',
|
key: 'callRate',
|
||||||
|
width: 150,
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.callRate - b.callRate,
|
sorter: (a: DataProps, b: DataProps): number => a.callRate - b.callRate,
|
||||||
render: (value: number): string => value.toFixed(2),
|
render: (value: number): string => value.toFixed(2),
|
||||||
},
|
},
|
||||||
@ -141,10 +146,10 @@ function Metrics(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={services}
|
dataSource={services}
|
||||||
columns={columns}
|
|
||||||
rowKey="serviceName"
|
rowKey="serviceName"
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -23,6 +23,8 @@ function PasswordContainer(): JSX.Element {
|
|||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPassword && !isPasswordValid(currentPassword)) {
|
if (currentPassword && !isPasswordValid(currentPassword)) {
|
||||||
setIsPasswordPolicyError(true);
|
setIsPasswordPolicyError(true);
|
||||||
@ -52,13 +54,13 @@ function PasswordContainer(): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (statusCode === 200) {
|
if (statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
error ||
|
error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -70,7 +72,7 @@ function PasswordContainer(): JSX.Element {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -80,6 +82,7 @@ function PasswordContainer(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" size="large">
|
<Space direction="vertical" size="large">
|
||||||
|
{NotificationElement}
|
||||||
<Typography.Title level={3}>
|
<Typography.Title level={3}>
|
||||||
{t('change_password', {
|
{t('change_password', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
|
@ -21,6 +21,8 @@ function UpdateName(): JSX.Element {
|
|||||||
const [changedName, setChangedName] = useState<string>(user?.name || '');
|
const [changedName, setChangedName] = useState<string>(user?.name || '');
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
if (!user || !org) {
|
if (!user || !org) {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
@ -34,7 +36,7 @@ function UpdateName(): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (statusCode === 200) {
|
if (statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -51,7 +53,7 @@ function UpdateName(): JSX.Element {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -59,7 +61,7 @@ function UpdateName(): JSX.Element {
|
|||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -70,6 +72,7 @@ function UpdateName(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{NotificationElement}
|
||||||
<Space direction="vertical" size="middle">
|
<Space direction="vertical" size="middle">
|
||||||
<Typography>Name</Typography>
|
<Typography>Name</Typography>
|
||||||
<NameInput
|
<NameInput
|
||||||
|
@ -22,6 +22,8 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
|
|||||||
(state) => state.dashboards,
|
(state) => state.dashboards,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const [selectedDashboard] = dashboards;
|
const [selectedDashboard] = dashboards;
|
||||||
const { data } = selectedDashboard;
|
const { data } = selectedDashboard;
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
|
|||||||
const emptyLayout = data.layout?.find((e) => e.i === 'empty');
|
const emptyLayout = data.layout?.find((e) => e.i === 'empty');
|
||||||
|
|
||||||
if (emptyLayout === undefined) {
|
if (emptyLayout === undefined) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Please click on Add Panel Button',
|
message: 'Please click on Add Panel Button',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -43,18 +45,19 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
|
|||||||
`${history.location.pathname}/new?graphType=${name}&widgetId=${emptyLayout.i}`,
|
`${history.location.pathname}/new?graphType=${name}&widgetId=${emptyLayout.i}`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: 'Something went wrong',
|
message: 'Something went wrong',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[data, toggleAddWidget],
|
[data, toggleAddWidget, notifications],
|
||||||
);
|
);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
const fillColor: React.CSSProperties['color'] = isDarkMode ? 'white' : 'black';
|
const fillColor: React.CSSProperties['color'] = isDarkMode ? 'white' : 'black';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
{NotificationElement}
|
||||||
{menuItems.map(({ name, Icon, display }) => (
|
{menuItems.map(({ name, Icon, display }) => (
|
||||||
<Card
|
<Card
|
||||||
onClick={(event): void => {
|
onClick={(event): void => {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { blue, red } from '@ant-design/colors';
|
import { blue, red } from '@ant-design/colors';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Modal, Row, Space, Table, Tag } from 'antd';
|
import { Button, Modal, notification, Row, Space, Tag } from 'antd';
|
||||||
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
@ -24,6 +26,8 @@ function VariablesSetting({
|
|||||||
(state) => state.dashboards,
|
(state) => state.dashboards,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const [selectedDashboard] = dashboards;
|
const [selectedDashboard] = dashboards;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -74,8 +78,7 @@ function VariablesSetting({
|
|||||||
if (oldName) {
|
if (oldName) {
|
||||||
delete newVariables[oldName];
|
delete newVariables[oldName];
|
||||||
}
|
}
|
||||||
|
updateDashboardVariables(newVariables, notifications);
|
||||||
updateDashboardVariables(newVariables);
|
|
||||||
onDoneVariableViewMode();
|
onDoneVariableViewMode();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,7 +90,7 @@ function VariablesSetting({
|
|||||||
const handleDeleteConfirm = (): void => {
|
const handleDeleteConfirm = (): void => {
|
||||||
const newVariables = { ...variables };
|
const newVariables = { ...variables };
|
||||||
if (variableToDelete?.current) delete newVariables[variableToDelete?.current];
|
if (variableToDelete?.current) delete newVariables[variableToDelete?.current];
|
||||||
updateDashboardVariables(newVariables);
|
updateDashboardVariables(newVariables, notifications);
|
||||||
variableToDelete.current = null;
|
variableToDelete.current = null;
|
||||||
setDeleteVariableModal(false);
|
setDeleteVariableModal(false);
|
||||||
};
|
};
|
||||||
@ -102,15 +105,18 @@ function VariablesSetting({
|
|||||||
{
|
{
|
||||||
title: 'Variable',
|
title: 'Variable',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
|
width: 100,
|
||||||
key: 'name',
|
key: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Definition',
|
title: 'Definition',
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
|
width: 100,
|
||||||
key: 'description',
|
key: 'description',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Actions',
|
title: 'Actions',
|
||||||
|
width: 50,
|
||||||
key: 'action',
|
key: 'action',
|
||||||
render: (_: IDashboardVariable): JSX.Element => (
|
render: (_: IDashboardVariable): JSX.Element => (
|
||||||
<Space>
|
<Space>
|
||||||
@ -137,6 +143,7 @@ function VariablesSetting({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
{variableViewMode ? (
|
{variableViewMode ? (
|
||||||
<VariableItem
|
<VariableItem
|
||||||
variableData={{ ...variableEditData } as IDashboardVariable}
|
variableData={{ ...variableEditData } as IDashboardVariable}
|
||||||
@ -158,7 +165,7 @@ function VariablesSetting({
|
|||||||
<PlusOutlined /> New Variables
|
<PlusOutlined /> New Variables
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
<Table columns={columns} dataSource={variablesTableData} />
|
<ResizeTable columns={columns} dataSource={variablesTableData} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Modal
|
<Modal
|
||||||
@ -178,6 +185,7 @@ function VariablesSetting({
|
|||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
updateDashboardVariables: (
|
updateDashboardVariables: (
|
||||||
props: Record<string, IDashboardVariable>,
|
props: Record<string, IDashboardVariable>,
|
||||||
|
notify: NotificationInstance,
|
||||||
) => (dispatch: Dispatch<AppActions>) => void;
|
) => (dispatch: Dispatch<AppActions>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Row } from 'antd';
|
import { notification, Row } from 'antd';
|
||||||
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
import { map, sortBy } from 'lodash-es';
|
import { map, sortBy } from 'lodash-es';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
@ -25,6 +26,7 @@ function DashboardVariableSelection({
|
|||||||
|
|
||||||
const [update, setUpdate] = useState<boolean>(false);
|
const [update, setUpdate] = useState<boolean>(false);
|
||||||
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onVarChanged = (name: string): void => {
|
const onVarChanged = (name: string): void => {
|
||||||
setLastUpdatedVar(name);
|
setLastUpdatedVar(name);
|
||||||
@ -45,7 +47,7 @@ function DashboardVariableSelection({
|
|||||||
): void => {
|
): void => {
|
||||||
const updatedVariablesData = { ...variables };
|
const updatedVariablesData = { ...variables };
|
||||||
updatedVariablesData[name].selectedValue = value;
|
updatedVariablesData[name].selectedValue = value;
|
||||||
updateDashboardVariables(updatedVariablesData);
|
updateDashboardVariables(updatedVariablesData, notifications);
|
||||||
onVarChanged(name);
|
onVarChanged(name);
|
||||||
};
|
};
|
||||||
const onAllSelectedUpdate = (
|
const onAllSelectedUpdate = (
|
||||||
@ -54,12 +56,13 @@ function DashboardVariableSelection({
|
|||||||
): void => {
|
): void => {
|
||||||
const updatedVariablesData = { ...variables };
|
const updatedVariablesData = { ...variables };
|
||||||
updatedVariablesData[name].allSelected = value;
|
updatedVariablesData[name].allSelected = value;
|
||||||
updateDashboardVariables(updatedVariablesData);
|
updateDashboardVariables(updatedVariablesData, notifications);
|
||||||
onVarChanged(name);
|
onVarChanged(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row style={{ gap: '1rem' }}>
|
<Row style={{ gap: '1rem' }}>
|
||||||
|
{NotificationElement}
|
||||||
{map(sortBy(Object.keys(variables)), (variableName) => (
|
{map(sortBy(Object.keys(variables)), (variableName) => (
|
||||||
<VariableItem
|
<VariableItem
|
||||||
key={`${variableName}${variables[variableName].modificationUUID}`}
|
key={`${variableName}${variables[variableName].modificationUUID}`}
|
||||||
@ -81,6 +84,7 @@ function DashboardVariableSelection({
|
|||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
updateDashboardVariables: (
|
updateDashboardVariables: (
|
||||||
props: Parameters<typeof UpdateDashboardVariables>[0],
|
props: Parameters<typeof UpdateDashboardVariables>[0],
|
||||||
|
notify: NotificationInstance,
|
||||||
) => (dispatch: Dispatch<AppActions>) => void;
|
) => (dispatch: Dispatch<AppActions>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +32,11 @@ function ShareModal({
|
|||||||
const [isViewJSON, setIsViewJSON] = useState<boolean>(false);
|
const [isViewJSON, setIsViewJSON] = useState<boolean>(false);
|
||||||
const { t } = useTranslation(['dashboard', 'common']);
|
const { t } = useTranslation(['dashboard', 'common']);
|
||||||
const [state, setCopy] = useCopyToClipboard();
|
const [state, setCopy] = useCopyToClipboard();
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.error) {
|
if (state.error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -43,13 +44,13 @@ function ShareModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.value) {
|
if (state.value) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [state.error, state.value, t]);
|
}, [state.error, state.value, t, notifications]);
|
||||||
|
|
||||||
const selectedDataCleaned = cleardQueryData(selectedData);
|
const selectedDataCleaned = cleardQueryData(selectedData);
|
||||||
const GetFooterComponent = useMemo(() => {
|
const GetFooterComponent = useMemo(() => {
|
||||||
@ -83,28 +84,34 @@ function ShareModal({
|
|||||||
}, [isViewJSON, jsonValue, selectedData, selectedDataCleaned, setCopy, t]);
|
}, [isViewJSON, jsonValue, selectedData, selectedDataCleaned, setCopy, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<>
|
||||||
open={isJSONModalVisible}
|
{NotificationElement}
|
||||||
onCancel={(): void => {
|
<Modal
|
||||||
onToggleHandler();
|
open={isJSONModalVisible}
|
||||||
setIsViewJSON(false);
|
onCancel={(): void => {
|
||||||
}}
|
onToggleHandler();
|
||||||
width="70vw"
|
setIsViewJSON(false);
|
||||||
centered
|
}}
|
||||||
title={t('share', {
|
width="70vw"
|
||||||
ns: 'common',
|
centered
|
||||||
})}
|
title={t('share', {
|
||||||
okText={t('download_json')}
|
ns: 'common',
|
||||||
cancelText={t('cancel')}
|
})}
|
||||||
destroyOnClose
|
okText={t('download_json')}
|
||||||
footer={GetFooterComponent}
|
cancelText={t('cancel')}
|
||||||
>
|
destroyOnClose
|
||||||
{!isViewJSON ? (
|
footer={GetFooterComponent}
|
||||||
<Typography>{t('export_dashboard')}</Typography>
|
>
|
||||||
) : (
|
{!isViewJSON ? (
|
||||||
<Editor onChange={(value): void => setJSONValue(value)} value={jsonValue} />
|
<Typography>{t('export_dashboard')}</Typography>
|
||||||
)}
|
) : (
|
||||||
</Modal>
|
<Editor
|
||||||
|
onChange={(value): void => setJSONValue(value)}
|
||||||
|
value={jsonValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ function QueryBuilderQueryContainer({
|
|||||||
metricsBuilderQueries,
|
metricsBuilderQueries,
|
||||||
selectedGraph,
|
selectedGraph,
|
||||||
}: IQueryBuilderQueryContainerProps): JSX.Element | null {
|
}: IQueryBuilderQueryContainerProps): JSX.Element | null {
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
const handleQueryBuilderQueryChange = ({
|
const handleQueryBuilderQueryChange = ({
|
||||||
queryIndex,
|
queryIndex,
|
||||||
aggregateFunction,
|
aggregateFunction,
|
||||||
@ -113,7 +114,7 @@ function QueryBuilderQueryContainer({
|
|||||||
};
|
};
|
||||||
const addQueryHandler = (): void => {
|
const addQueryHandler = (): void => {
|
||||||
if (!canCreateQueryAndFormula(queryData)) {
|
if (!canCreateQueryAndFormula(queryData)) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
'Unable to create query. You can create at max 10 queries and formulae.',
|
'Unable to create query. You can create at max 10 queries and formulae.',
|
||||||
});
|
});
|
||||||
@ -130,7 +131,7 @@ function QueryBuilderQueryContainer({
|
|||||||
|
|
||||||
const addFormulaHandler = (): void => {
|
const addFormulaHandler = (): void => {
|
||||||
if (!canCreateQueryAndFormula(queryData)) {
|
if (!canCreateQueryAndFormula(queryData)) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
'Unable to create formula. You can create at max 10 queries and formulae.',
|
'Unable to create formula. You can create at max 10 queries and formulae.',
|
||||||
});
|
});
|
||||||
@ -155,6 +156,7 @@ function QueryBuilderQueryContainer({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
{metricsBuilderQueries.queryBuilder.map((q, idx) => (
|
{metricsBuilderQueries.queryBuilder.map((q, idx) => (
|
||||||
<MetricsBuilder
|
<MetricsBuilder
|
||||||
key={q.name}
|
key={q.name}
|
||||||
|
@ -74,7 +74,6 @@ function QuerySection({
|
|||||||
setLocalQueryChanges(cloneDeep(query) as Query);
|
setLocalQueryChanges(cloneDeep(query) as Query);
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
|
|
||||||
const queryDiff = (
|
const queryDiff = (
|
||||||
queryA: Query,
|
queryA: Query,
|
||||||
queryB: Query,
|
queryB: Query,
|
||||||
@ -142,7 +141,7 @@ function QuerySection({
|
|||||||
const handleLocalQueryUpdate = ({
|
const handleLocalQueryUpdate = ({
|
||||||
updatedQuery,
|
updatedQuery,
|
||||||
}: IHandleUpdatedQuery): void => {
|
}: IHandleUpdatedQuery): void => {
|
||||||
setLocalQueryChanges(updatedQuery);
|
setLocalQueryChanges(cloneDeep(updatedQuery));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -21,6 +21,8 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
|||||||
|
|
||||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onCreateHandler = async (): Promise<void> => {
|
const onCreateHandler = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const response = await createDomainApi({
|
const response = await createDomainApi({
|
||||||
@ -29,19 +31,19 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: 'Your domain has been added successfully.',
|
message: 'Your domain has been added successfully.',
|
||||||
duration: 15,
|
duration: 15,
|
||||||
});
|
});
|
||||||
setIsDomain(false);
|
setIsDomain(false);
|
||||||
refetch();
|
refetch();
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('common:something_went_wrong'),
|
message: t('common:something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('common:something_went_wrong'),
|
message: t('common:something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -49,6 +51,7 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
<Container>
|
<Container>
|
||||||
<Typography.Title level={3}>
|
<Typography.Title level={3}>
|
||||||
{t('authenticated_domains', {
|
{t('authenticated_domains', {
|
||||||
|
@ -31,6 +31,8 @@ function EditSSO({
|
|||||||
|
|
||||||
const { t } = useTranslation(['common']);
|
const { t } = useTranslation(['common']);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onFinishHandler = useCallback(() => {
|
const onFinishHandler = useCallback(() => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@ -44,11 +46,11 @@ function EditSSO({
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', { ns: 'common' }),
|
message: t('something_went_wrong', { ns: 'common' }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [form, onRecordUpdateHandler, record, t]);
|
}, [form, onRecordUpdateHandler, record, t, notifications]);
|
||||||
|
|
||||||
const onResetHandler = useCallback(() => {
|
const onResetHandler = useCallback(() => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@ -61,7 +63,7 @@ function EditSSO({
|
|||||||
initialValues={record}
|
initialValues={record}
|
||||||
onFinishFailed={(error): void => {
|
onFinishFailed={(error): void => {
|
||||||
error.errorFields.forEach(({ errors }) => {
|
error.errorFields.forEach(({ errors }) => {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
errors[0].toString() || t('something_went_wrong', { ns: 'common' }),
|
errors[0].toString() || t('something_went_wrong', { ns: 'common' }),
|
||||||
});
|
});
|
||||||
@ -73,6 +75,7 @@ function EditSSO({
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
form={form}
|
form={form}
|
||||||
>
|
>
|
||||||
|
{NotificationElement}
|
||||||
{renderFormInputs(record)}
|
{renderFormInputs(record)}
|
||||||
<Space
|
<Space
|
||||||
style={{ width: '100%', justifyContent: 'flex-end' }}
|
style={{ width: '100%', justifyContent: 'flex-end' }}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { LockTwoTone } from '@ant-design/icons';
|
import { LockTwoTone } from '@ant-design/icons';
|
||||||
import { Button, Modal, notification, Space, Table, Typography } from 'antd';
|
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import deleteDomain from 'api/SAML/deleteDomain';
|
import deleteDomain from 'api/SAML/deleteDomain';
|
||||||
import listAllDomain from 'api/SAML/listAllDomain';
|
import listAllDomain from 'api/SAML/listAllDomain';
|
||||||
import updateDomain from 'api/SAML/updateDomain';
|
import updateDomain from 'api/SAML/updateDomain';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
||||||
import { FeatureKeys } from 'constants/featureKeys';
|
import { FeatureKeys } from 'constants/featureKeys';
|
||||||
@ -56,6 +57,8 @@ function AuthDomains(): JSX.Element {
|
|||||||
enabled: org !== null,
|
enabled: org !== null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const assignSsoMethod = useCallback(
|
const assignSsoMethod = useCallback(
|
||||||
(typ: AuthDomain['ssoType']): void => {
|
(typ: AuthDomain['ssoType']): void => {
|
||||||
setCurrentDomain({ ...currentDomain, ssoType: typ } as AuthDomain);
|
setCurrentDomain({ ...currentDomain, ssoType: typ } as AuthDomain);
|
||||||
@ -76,7 +79,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
const response = await updateDomain(record);
|
const response = await updateDomain(record);
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('saml_settings', {
|
message: t('saml_settings', {
|
||||||
ns: 'organizationsettings',
|
ns: 'organizationsettings',
|
||||||
}),
|
}),
|
||||||
@ -87,7 +90,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -95,7 +98,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -103,7 +106,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[refetch, t, onCloseHandler],
|
[refetch, t, onCloseHandler, notifications],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onOpenHandler = useCallback(
|
const onOpenHandler = useCallback(
|
||||||
@ -142,19 +145,19 @@ function AuthDomains(): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('common:success'),
|
message: t('common:success'),
|
||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('common:something_went_wrong'),
|
message: t('common:something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[refetch, t],
|
[refetch, t, notifications],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClickLicenseHandler = useCallback(() => {
|
const onClickLicenseHandler = useCallback(() => {
|
||||||
@ -166,6 +169,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
title: 'Domain',
|
title: 'Domain',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: (
|
title: (
|
||||||
@ -181,6 +185,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
),
|
),
|
||||||
dataIndex: 'ssoEnabled',
|
dataIndex: 'ssoEnabled',
|
||||||
key: 'ssoEnabled',
|
key: 'ssoEnabled',
|
||||||
|
width: 80,
|
||||||
render: (value: boolean, record: AuthDomain): JSX.Element => {
|
render: (value: boolean, record: AuthDomain): JSX.Element => {
|
||||||
if (!SSOFlag) {
|
if (!SSOFlag) {
|
||||||
return (
|
return (
|
||||||
@ -207,6 +212,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
title: '',
|
title: '',
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
key: 'description',
|
key: 'description',
|
||||||
|
width: 100,
|
||||||
render: (_, record: AuthDomain): JSX.Element => {
|
render: (_, record: AuthDomain): JSX.Element => {
|
||||||
if (!SSOFlag) {
|
if (!SSOFlag) {
|
||||||
return (
|
return (
|
||||||
@ -231,6 +237,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
title: 'Action',
|
title: 'Action',
|
||||||
dataIndex: 'action',
|
dataIndex: 'action',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
width: 50,
|
||||||
render: (_, record): JSX.Element => (
|
render: (_, record): JSX.Element => (
|
||||||
<Button
|
<Button
|
||||||
disabled={!SSOFlag}
|
disabled={!SSOFlag}
|
||||||
@ -247,6 +254,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
if (!isLoading && data?.payload?.length === 0) {
|
if (!isLoading && data?.payload?.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" size="middle">
|
<Space direction="vertical" size="middle">
|
||||||
|
{NotificationElement}
|
||||||
<AddDomain refetch={refetch} />
|
<AddDomain refetch={refetch} />
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
@ -264,10 +272,10 @@ function AuthDomains(): JSX.Element {
|
|||||||
setIsSettingsOpen={setIsSettingsOpen}
|
setIsSettingsOpen={setIsSettingsOpen}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
rowKey={(record: AuthDomain): string => record.name + v4()}
|
rowKey={(record: AuthDomain): string => record.name + v4()}
|
||||||
dataSource={!SSOFlag ? notEntripriseData : []}
|
dataSource={!SSOFlag ? notEntripriseData : []}
|
||||||
columns={columns}
|
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
@ -278,6 +286,7 @@ function AuthDomains(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
<Modal
|
<Modal
|
||||||
centered
|
centered
|
||||||
title="Configure Authentication Method"
|
title="Configure Authentication Method"
|
||||||
@ -313,10 +322,10 @@ function AuthDomains(): JSX.Element {
|
|||||||
<Space direction="vertical" size="middle">
|
<Space direction="vertical" size="middle">
|
||||||
<AddDomain refetch={refetch} />
|
<AddDomain refetch={refetch} />
|
||||||
|
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
dataSource={tableData}
|
dataSource={tableData}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
columns={columns}
|
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
rowKey={(record: AuthDomain): string => record.name + v4()}
|
rowKey={(record: AuthDomain): string => record.name + v4()}
|
||||||
/>
|
/>
|
||||||
|
@ -21,6 +21,7 @@ function DisplayName({
|
|||||||
const { name } = (org || [])[index];
|
const { name } = (org || [])[index];
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onSubmit = async ({ name: orgName }: OnSubmitProps): Promise<void> => {
|
const onSubmit = async ({ name: orgName }: OnSubmitProps): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@ -31,7 +32,7 @@ function DisplayName({
|
|||||||
orgId,
|
orgId,
|
||||||
});
|
});
|
||||||
if (statusCode === 200) {
|
if (statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -44,7 +45,7 @@ function DisplayName({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
error ||
|
error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -55,7 +56,7 @@ function DisplayName({
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -75,6 +76,7 @@ function DisplayName({
|
|||||||
onFinish={onSubmit}
|
onFinish={onSubmit}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
|
{NotificationElement}
|
||||||
<Form.Item name="name" label="Display name" rules={[{ required: true }]}>
|
<Form.Item name="name" label="Display name" rules={[{ required: true }]}>
|
||||||
<Input size="large" placeholder={t('signoz')} disabled={isLoading} />
|
<Input size="large" placeholder={t('signoz')} disabled={isLoading} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -36,19 +36,21 @@ function EditMembersDetails({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.error) {
|
if (state.error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong'),
|
message: t('something_went_wrong'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.value) {
|
if (state.value) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success'),
|
message: t('success'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [state.error, state.value, t]);
|
}, [state.error, state.value, t, notifications]);
|
||||||
|
|
||||||
const onPasswordChangeHandler: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
const onPasswordChangeHandler: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
@ -67,7 +69,7 @@ function EditMembersDetails({
|
|||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
setPasswordLink(getPasswordLink(response.payload.token));
|
setPasswordLink(getPasswordLink(response.payload.token));
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
response.error ||
|
response.error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -79,7 +81,7 @@ function EditMembersDetails({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -89,6 +91,7 @@ function EditMembersDetails({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" size="large">
|
<Space direction="vertical" size="large">
|
||||||
|
{NotificationElement}
|
||||||
<Space direction="horizontal">
|
<Space direction="horizontal">
|
||||||
<Title>Email address</Title>
|
<Title>Email address</Title>
|
||||||
<Input
|
<Input
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Button, Modal, notification, Space, Table, Typography } from 'antd';
|
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import deleteUser from 'api/user/deleteUser';
|
import deleteUser from 'api/user/deleteUser';
|
||||||
import editUserApi from 'api/user/editUser';
|
import editUserApi from 'api/user/editUser';
|
||||||
import getOrgUser from 'api/user/getOrgUser';
|
import getOrgUser from 'api/user/getOrgUser';
|
||||||
import updateRole from 'api/user/updateRole';
|
import updateRole from 'api/user/updateRole';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -39,6 +40,7 @@ function UserFunction({
|
|||||||
const { t } = useTranslation(['common']);
|
const { t } = useTranslation(['common']);
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState<boolean>(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState<boolean>(false);
|
||||||
const [isUpdateLoading, setIsUpdateLoading] = useState<boolean>(false);
|
const [isUpdateLoading, setIsUpdateLoading] = useState<boolean>(false);
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
const onUpdateDetailsHandler = (): void => {
|
const onUpdateDetailsHandler = (): void => {
|
||||||
setDataSource((data) => {
|
setDataSource((data) => {
|
||||||
@ -88,14 +90,14 @@ function UserFunction({
|
|||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
onDelete();
|
onDelete();
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setIsDeleteModalVisible(false);
|
setIsDeleteModalVisible(false);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
response.error ||
|
response.error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -107,7 +109,7 @@ function UserFunction({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -134,13 +136,13 @@ function UserFunction({
|
|||||||
updateRoleResponse.statusCode === 200
|
updateRoleResponse.statusCode === 200
|
||||||
) {
|
) {
|
||||||
onUpdateDetailsHandler();
|
onUpdateDetailsHandler();
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
editUserResponse.error ||
|
editUserResponse.error ||
|
||||||
updateRoleResponse.error ||
|
updateRoleResponse.error ||
|
||||||
@ -151,7 +153,7 @@ function UserFunction({
|
|||||||
}
|
}
|
||||||
setIsUpdateLoading(false);
|
setIsUpdateLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -162,6 +164,7 @@ function UserFunction({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
<Space direction="horizontal">
|
<Space direction="horizontal">
|
||||||
<Typography.Link
|
<Typography.Link
|
||||||
onClick={(): void => onModalToggleHandler(setIsModalVisible, true)}
|
onClick={(): void => onModalToggleHandler(setIsModalVisible, true)}
|
||||||
@ -256,21 +259,25 @@ function Members(): JSX.Element {
|
|||||||
title: 'Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Emails',
|
title: 'Emails',
|
||||||
dataIndex: 'email',
|
dataIndex: 'email',
|
||||||
key: 'email',
|
key: 'email',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Access Level',
|
title: 'Access Level',
|
||||||
dataIndex: 'accessLevel',
|
dataIndex: 'accessLevel',
|
||||||
key: 'accessLevel',
|
key: 'accessLevel',
|
||||||
|
width: 50,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Joined On',
|
title: 'Joined On',
|
||||||
dataIndex: 'joinedOn',
|
dataIndex: 'joinedOn',
|
||||||
key: 'joinedOn',
|
key: 'joinedOn',
|
||||||
|
width: 60,
|
||||||
render: (_, record): JSX.Element => {
|
render: (_, record): JSX.Element => {
|
||||||
const { joinedOn } = record;
|
const { joinedOn } = record;
|
||||||
return (
|
return (
|
||||||
@ -283,6 +290,7 @@ function Members(): JSX.Element {
|
|||||||
{
|
{
|
||||||
title: 'Action',
|
title: 'Action',
|
||||||
dataIndex: 'action',
|
dataIndex: 'action',
|
||||||
|
width: 80,
|
||||||
render: (_, record): JSX.Element => (
|
render: (_, record): JSX.Element => (
|
||||||
<UserFunction
|
<UserFunction
|
||||||
{...{
|
{...{
|
||||||
@ -301,10 +309,10 @@ function Members(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Space direction="vertical" size="middle">
|
<Space direction="vertical" size="middle">
|
||||||
<Typography.Title level={3}>Members</Typography.Title>
|
<Typography.Title level={3}>Members</Typography.Title>
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
columns={columns}
|
|
||||||
pagination={false}
|
pagination={false}
|
||||||
loading={status === 'loading'}
|
loading={status === 'loading'}
|
||||||
/>
|
/>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Modal, notification, Space, Table, Typography } from 'antd';
|
import { Button, Modal, notification, Space, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import deleteInvite from 'api/user/deleteInvite';
|
import deleteInvite from 'api/user/deleteInvite';
|
||||||
import getPendingInvites from 'api/user/getPendingInvites';
|
import getPendingInvites from 'api/user/getPendingInvites';
|
||||||
import sendInvite from 'api/user/sendInvite';
|
import sendInvite from 'api/user/sendInvite';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { INVITE_MEMBERS_HASH } from 'constants/app';
|
import { INVITE_MEMBERS_HASH } from 'constants/app';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
@ -25,22 +26,23 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
const [isInvitingMembers, setIsInvitingMembers] = useState<boolean>(false);
|
const [isInvitingMembers, setIsInvitingMembers] = useState<boolean>(false);
|
||||||
const { t } = useTranslation(['organizationsettings', 'common']);
|
const { t } = useTranslation(['organizationsettings', 'common']);
|
||||||
const [state, setText] = useCopyToClipboard();
|
const [state, setText] = useCopyToClipboard();
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.error) {
|
if (state.error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: state.error.message,
|
message: state.error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.value) {
|
if (state.value) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [state.error, state.value, t]);
|
}, [state.error, state.value, t, notifications]);
|
||||||
|
|
||||||
const getPendingInvitesResponse = useQuery({
|
const getPendingInvitesResponse = useQuery({
|
||||||
queryFn: () => getPendingInvites(),
|
queryFn: () => getPendingInvites(),
|
||||||
@ -112,13 +114,13 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
...dataSource.slice(index + 1, dataSource.length),
|
...dataSource.slice(index + 1, dataSource.length),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
response.error ||
|
response.error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -127,7 +129,7 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -140,26 +142,31 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
title: 'Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Emails',
|
title: 'Emails',
|
||||||
dataIndex: 'email',
|
dataIndex: 'email',
|
||||||
key: 'email',
|
key: 'email',
|
||||||
|
width: 80,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Access Level',
|
title: 'Access Level',
|
||||||
dataIndex: 'accessLevel',
|
dataIndex: 'accessLevel',
|
||||||
key: 'accessLevel',
|
key: 'accessLevel',
|
||||||
|
width: 50,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Invite Link',
|
title: 'Invite Link',
|
||||||
dataIndex: 'inviteLink',
|
dataIndex: 'inviteLink',
|
||||||
key: 'Invite Link',
|
key: 'Invite Link',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Action',
|
title: 'Action',
|
||||||
dataIndex: 'action',
|
dataIndex: 'action',
|
||||||
|
width: 80,
|
||||||
key: 'Action',
|
key: 'Action',
|
||||||
render: (_, record): JSX.Element => (
|
render: (_, record): JSX.Element => (
|
||||||
<Space direction="horizontal">
|
<Space direction="horizontal">
|
||||||
@ -192,7 +199,7 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (statusCode !== 200) {
|
if (statusCode !== 200) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
error ||
|
error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -212,7 +219,7 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
toggleModal(false);
|
toggleModal(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -222,6 +229,7 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{NotificationElement}
|
||||||
<Modal
|
<Modal
|
||||||
title={t('invite_team_members')}
|
title={t('invite_team_members')}
|
||||||
open={isInviteTeamMemberModalOpen}
|
open={isInviteTeamMemberModalOpen}
|
||||||
@ -261,10 +269,10 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
{t('invite_members')}
|
{t('invite_members')}
|
||||||
</Button>
|
</Button>
|
||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
columns={columns}
|
|
||||||
pagination={false}
|
pagination={false}
|
||||||
loading={getPendingInvitesResponse.status === 'loading'}
|
loading={getPendingInvitesResponse.status === 'loading'}
|
||||||
/>
|
/>
|
||||||
|
@ -24,6 +24,7 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
|||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const params = new URLSearchParams(search);
|
const params = new URLSearchParams(search);
|
||||||
const token = params.get('token');
|
const token = params.get('token');
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@ -53,14 +54,14 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
notification.success({
|
notifications.success({
|
||||||
message: t('success', {
|
message: t('success', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
history.push(ROUTES.LOGIN);
|
history.push(ROUTES.LOGIN);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
response.error ||
|
response.error ||
|
||||||
t('something_went_wrong', {
|
t('something_went_wrong', {
|
||||||
@ -72,7 +73,7 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: t('something_went_wrong', {
|
message: t('something_went_wrong', {
|
||||||
ns: 'common',
|
ns: 'common',
|
||||||
}),
|
}),
|
||||||
@ -82,70 +83,73 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WelcomeLeftContainer version={version}>
|
<WelcomeLeftContainer version={version}>
|
||||||
<FormWrapper>
|
<>
|
||||||
<form onSubmit={handleSubmit}>
|
{NotificationElement}
|
||||||
<Title level={4}>Reset Your Password</Title>
|
<FormWrapper>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Title level={4}>Reset Your Password</Title>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="Password">Password</Label>
|
<Label htmlFor="Password">Password</Label>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e): void => {
|
onChange={(e): void => {
|
||||||
setState(e.target.value, setPassword);
|
setState(e.target.value, setPassword);
|
||||||
}}
|
|
||||||
required
|
|
||||||
id="currentPassword"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="ConfirmPassword">Confirm Password</Label>
|
|
||||||
<Input.Password
|
|
||||||
value={confirmPassword}
|
|
||||||
onChange={(e): void => {
|
|
||||||
const updateValue = e.target.value;
|
|
||||||
setState(updateValue, setConfirmPassword);
|
|
||||||
if (password !== updateValue) {
|
|
||||||
setConfirmPasswordError(true);
|
|
||||||
} else {
|
|
||||||
setConfirmPasswordError(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
id="UpdatePassword"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{confirmPasswordError && (
|
|
||||||
<Typography.Paragraph
|
|
||||||
italic
|
|
||||||
style={{
|
|
||||||
color: '#D89614',
|
|
||||||
marginTop: '0.50rem',
|
|
||||||
}}
|
}}
|
||||||
>
|
required
|
||||||
Passwords don’t match. Please try again
|
id="currentPassword"
|
||||||
</Typography.Paragraph>
|
/>
|
||||||
)}
|
</div>
|
||||||
</div>
|
<div>
|
||||||
|
<Label htmlFor="ConfirmPassword">Confirm Password</Label>
|
||||||
|
<Input.Password
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e): void => {
|
||||||
|
const updateValue = e.target.value;
|
||||||
|
setState(updateValue, setConfirmPassword);
|
||||||
|
if (password !== updateValue) {
|
||||||
|
setConfirmPasswordError(true);
|
||||||
|
} else {
|
||||||
|
setConfirmPasswordError(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
id="UpdatePassword"
|
||||||
|
/>
|
||||||
|
|
||||||
<ButtonContainer>
|
{confirmPasswordError && (
|
||||||
<Button
|
<Typography.Paragraph
|
||||||
type="primary"
|
italic
|
||||||
htmlType="submit"
|
style={{
|
||||||
data-attr="signup"
|
color: '#D89614',
|
||||||
loading={loading}
|
marginTop: '0.50rem',
|
||||||
disabled={
|
}}
|
||||||
loading ||
|
>
|
||||||
!password ||
|
Passwords don’t match. Please try again
|
||||||
!confirmPassword ||
|
</Typography.Paragraph>
|
||||||
confirmPasswordError ||
|
)}
|
||||||
token === null
|
</div>
|
||||||
}
|
|
||||||
>
|
<ButtonContainer>
|
||||||
Get Started
|
<Button
|
||||||
</Button>
|
type="primary"
|
||||||
</ButtonContainer>
|
htmlType="submit"
|
||||||
</form>
|
data-attr="signup"
|
||||||
</FormWrapper>
|
loading={loading}
|
||||||
|
disabled={
|
||||||
|
loading ||
|
||||||
|
!password ||
|
||||||
|
!confirmPassword ||
|
||||||
|
confirmPasswordError ||
|
||||||
|
token === null
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</Button>
|
||||||
|
</ButtonContainer>
|
||||||
|
</form>
|
||||||
|
</FormWrapper>
|
||||||
|
</>
|
||||||
</WelcomeLeftContainer>
|
</WelcomeLeftContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,8 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
|||||||
(userSelectedFilter.get(name) || []).find((e) => e === keyValue) !==
|
(userSelectedFilter.get(name) || []).find((e) => e === keyValue) !==
|
||||||
undefined;
|
undefined;
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const onCheckHandler = async (): Promise<void> => {
|
const onCheckHandler = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@ -141,12 +143,12 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
|||||||
} else {
|
} else {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: response.error || 'Something went wrong',
|
message: response.error || 'Something went wrong',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: (error as AxiosError).toString() || 'Something went wrong',
|
message: (error as AxiosError).toString() || 'Something went wrong',
|
||||||
});
|
});
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -161,6 +163,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CheckBoxContainer>
|
<CheckBoxContainer>
|
||||||
|
{NotificationElement}
|
||||||
<Checkbox
|
<Checkbox
|
||||||
disabled={isLoading || filterLoading}
|
disabled={isLoading || filterLoading}
|
||||||
onClick={onCheckHandler}
|
onClick={onCheckHandler}
|
||||||
|
@ -28,6 +28,7 @@ function TraceID(): JSX.Element {
|
|||||||
);
|
);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [userEnteredValue, setUserEnteredValue] = useState<string>('');
|
const [userEnteredValue, setUserEnteredValue] = useState<string>('');
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUserEnteredValue(selectedFilter.get('traceID')?.[0] || '');
|
setUserEnteredValue(selectedFilter.get('traceID')?.[0] || '');
|
||||||
}, [selectedFilter]);
|
}, [selectedFilter]);
|
||||||
@ -91,7 +92,7 @@ function TraceID(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: (error as AxiosError).toString() || 'Something went wrong',
|
message: (error as AxiosError).toString() || 'Something went wrong',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@ -108,6 +109,7 @@ function TraceID(): JSX.Element {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{NotificationElement}
|
||||||
<Search
|
<Search
|
||||||
placeholder="Filter by Trace ID"
|
placeholder="Filter by Trace ID"
|
||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
|
@ -53,6 +53,8 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
|||||||
|
|
||||||
const defaultErrorMessage = 'Something went wrong';
|
const defaultErrorMessage = 'Something went wrong';
|
||||||
|
|
||||||
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const onExpandHandler: React.MouseEventHandler<HTMLDivElement> = async (e) => {
|
const onExpandHandler: React.MouseEventHandler<HTMLDivElement> = async (e) => {
|
||||||
try {
|
try {
|
||||||
@ -119,14 +121,14 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
|||||||
spansAggregate.orderParam,
|
spansAggregate.orderParam,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: response.error || defaultErrorMessage,
|
message: response.error || defaultErrorMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: (error as AxiosError).toString() || defaultErrorMessage,
|
message: (error as AxiosError).toString() || defaultErrorMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -225,13 +227,13 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
|||||||
spansAggregate.orderParam,
|
spansAggregate.orderParam,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: response.error || 'Something went wrong',
|
message: response.error || 'Something went wrong',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notifications.error({
|
||||||
message: (error as AxiosError).toString(),
|
message: (error as AxiosError).toString(),
|
||||||
});
|
});
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -295,6 +297,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{NotificationElement}
|
||||||
{PanelName !== 'duration' && <Divider plain style={{ margin: 0 }} />}
|
{PanelName !== 'duration' && <Divider plain style={{ margin: 0 }} />}
|
||||||
|
|
||||||
<Card bordered={false}>
|
<Card bordered={false}>
|
||||||
|
@ -22,7 +22,7 @@ function TraceGraph(): JSX.Element {
|
|||||||
|
|
||||||
const ChartData = useMemo(
|
const ChartData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
selectedGroupBy.length === 0
|
selectedGroupBy.length === 0 || selectedGroupBy === 'none'
|
||||||
? getChartData(payload)
|
? getChartData(payload)
|
||||||
: getChartDataforGroupBy(payload),
|
: getChartDataforGroupBy(payload),
|
||||||
[payload, selectedGroupBy],
|
[payload, selectedGroupBy],
|
||||||
|
@ -9,13 +9,12 @@ interface Dropdown {
|
|||||||
export const groupBy: DefaultOptionType[] = [
|
export const groupBy: DefaultOptionType[] = [
|
||||||
{
|
{
|
||||||
label: 'None',
|
label: 'None',
|
||||||
value: '',
|
value: 'none',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Service Name',
|
label: 'Service Name',
|
||||||
value: 'serviceName',
|
value: 'serviceName',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'Operation',
|
label: 'Operation',
|
||||||
value: 'name',
|
value: 'name',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AutoComplete, Input, Space } from 'antd';
|
import { AutoComplete, Input, Space } from 'antd';
|
||||||
import getTagFilters from 'api/trace/getTagFilter';
|
import getTagFilters from 'api/trace/getTagFilter';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -10,6 +10,7 @@ import { TraceReducer } from 'types/reducer/trace';
|
|||||||
import { functions } from './config';
|
import { functions } from './config';
|
||||||
import { SelectComponent } from './styles';
|
import { SelectComponent } from './styles';
|
||||||
import {
|
import {
|
||||||
|
filterGroupBy,
|
||||||
getSelectedValue,
|
getSelectedValue,
|
||||||
initOptions,
|
initOptions,
|
||||||
onClickSelectedFunctionHandler,
|
onClickSelectedFunctionHandler,
|
||||||
@ -24,6 +25,9 @@ function TraceGraphFilter(): JSX.Element {
|
|||||||
AppState,
|
AppState,
|
||||||
TraceReducer
|
TraceReducer
|
||||||
>((state) => state.traces);
|
>((state) => state.traces);
|
||||||
|
const [selectedGroupByLocal, setSelectedGroupByLocal] = useState<string>(
|
||||||
|
selectedGroupBy,
|
||||||
|
);
|
||||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
@ -75,8 +79,12 @@ function TraceGraphFilter(): JSX.Element {
|
|||||||
id="selectedGroupBy"
|
id="selectedGroupBy"
|
||||||
data-testid="selectedGroupBy"
|
data-testid="selectedGroupBy"
|
||||||
options={options}
|
options={options}
|
||||||
value={selectedGroupByValue(selectedGroupBy, options)}
|
value={selectedGroupByValue(selectedGroupByLocal, options)}
|
||||||
onChange={onClickSelectedGroupByHandler(options)}
|
onChange={(e): void => setSelectedGroupByLocal(e.toString())}
|
||||||
|
onSelect={onClickSelectedGroupByHandler(options)}
|
||||||
|
filterOption={(inputValue, option): boolean =>
|
||||||
|
filterGroupBy(inputValue, option)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Input disabled={isLoading} placeholder="Please select" />
|
<Input disabled={isLoading} placeholder="Please select" />
|
||||||
</AutoComplete>
|
</AutoComplete>
|
||||||
|
27
frontend/src/container/Trace/TraceGraphFilter/utils.test.ts
Normal file
27
frontend/src/container/Trace/TraceGraphFilter/utils.test.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { selectedGroupByValue } from './utils';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: '1',
|
||||||
|
label: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '2',
|
||||||
|
label: '2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('TraceGraphFilter/utils', () => {
|
||||||
|
it('should return the correct value', () => {
|
||||||
|
const selectedGroupBy = '1';
|
||||||
|
const result = selectedGroupByValue(selectedGroupBy, options);
|
||||||
|
expect(result).toEqual(selectedGroupBy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct value when selectedOption not found', () => {
|
||||||
|
const selectedGroupBy = '3';
|
||||||
|
|
||||||
|
const result = selectedGroupByValue(selectedGroupBy, options);
|
||||||
|
expect(result).toEqual(selectedGroupBy);
|
||||||
|
});
|
||||||
|
});
|
@ -61,13 +61,28 @@ export function onClickSelectedFunctionHandler(value: unknown): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectedGroupByValue(
|
export function selectedGroupByValue(
|
||||||
selectedGroupBy: string,
|
selectedGroupBy: string,
|
||||||
options: DefaultOptionType[],
|
options: DefaultOptionType[],
|
||||||
): ReactNode {
|
): ReactNode {
|
||||||
return options.find((e) => selectedGroupBy === e.value)?.label;
|
const optionValue = options.find((e) => selectedGroupBy === e.value)?.label;
|
||||||
|
if (optionValue) {
|
||||||
|
return optionValue;
|
||||||
|
}
|
||||||
|
return selectedGroupBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSelectedValue(selectedFunction: string): unknown {
|
export function getSelectedValue(selectedFunction: string): unknown {
|
||||||
return functions.find((e) => selectedFunction === e.key)?.displayValue;
|
return functions.find((e) => selectedFunction === e.key)?.displayValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterGroupBy(
|
||||||
|
inputValue: string,
|
||||||
|
option: DefaultOptionType | undefined,
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
option?.label?.toString().toUpperCase().indexOf(inputValue.toUpperCase()) !==
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Table, TableProps, Tag, Typography } from 'antd';
|
import { TableProps, Tag, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import {
|
import {
|
||||||
getSpanOrder,
|
getSpanOrder,
|
||||||
@ -73,6 +74,7 @@ function TraceTable(): JSX.Element {
|
|||||||
title: 'Date',
|
title: 'Date',
|
||||||
dataIndex: 'timestamp',
|
dataIndex: 'timestamp',
|
||||||
key: 'timestamp',
|
key: 'timestamp',
|
||||||
|
width: 120,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (value: TableType['timestamp']): JSX.Element => {
|
render: (value: TableType['timestamp']): JSX.Element => {
|
||||||
const day = dayjs(value);
|
const day = dayjs(value);
|
||||||
@ -83,18 +85,21 @@ function TraceTable(): JSX.Element {
|
|||||||
title: 'Service',
|
title: 'Service',
|
||||||
dataIndex: 'serviceName',
|
dataIndex: 'serviceName',
|
||||||
key: 'serviceName',
|
key: 'serviceName',
|
||||||
|
width: 50,
|
||||||
render: getValue,
|
render: getValue,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Operation',
|
title: 'Operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
key: 'operation',
|
key: 'operation',
|
||||||
|
width: 110,
|
||||||
render: getValue,
|
render: getValue,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Duration',
|
title: 'Duration',
|
||||||
dataIndex: 'durationNano',
|
dataIndex: 'durationNano',
|
||||||
key: 'durationNano',
|
key: 'durationNano',
|
||||||
|
width: 50,
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (value: TableType['durationNano']): JSX.Element => (
|
render: (value: TableType['durationNano']): JSX.Element => (
|
||||||
<Typography>
|
<Typography>
|
||||||
@ -109,12 +114,14 @@ function TraceTable(): JSX.Element {
|
|||||||
title: 'Method',
|
title: 'Method',
|
||||||
dataIndex: 'method',
|
dataIndex: 'method',
|
||||||
key: 'method',
|
key: 'method',
|
||||||
|
width: 50,
|
||||||
render: getHttpMethodOrStatus,
|
render: getHttpMethodOrStatus,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Status Code',
|
title: 'Status Code',
|
||||||
dataIndex: 'statusCode',
|
dataIndex: 'statusCode',
|
||||||
key: 'statusCode',
|
key: 'statusCode',
|
||||||
|
width: 50,
|
||||||
render: getHttpMethodOrStatus,
|
render: getHttpMethodOrStatus,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -180,16 +187,18 @@ function TraceTable(): JSX.Element {
|
|||||||
) as number;
|
) as number;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<ResizeTable
|
||||||
|
columns={columns}
|
||||||
onChange={onChangeHandler}
|
onChange={onChangeHandler}
|
||||||
dataSource={spansAggregate.data}
|
dataSource={spansAggregate.data}
|
||||||
loading={loading || filterLoading}
|
loading={loading || filterLoading}
|
||||||
columns={columns}
|
rowKey={(record: { traceID: string; spanID: string }): string =>
|
||||||
rowKey={(record): string => `${record.traceID}-${record.spanID}-${v4()}`}
|
`${record.traceID}-${record.spanID}-${v4()}`
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
onRow={(record): React.HTMLAttributes<TableType> => ({
|
onRow={(record: TableType): React.HTMLAttributes<TableType> => ({
|
||||||
onClick: (event): void => {
|
onClick: (event): void => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
/**
|
import { render, renderHook } from '@testing-library/react';
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
|
||||||
import TraceFlameGraph from 'container/TraceFlameGraph';
|
import TraceFlameGraph from 'container/TraceFlameGraph';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
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