mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-03 20:20:37 +08:00
commit
0ff4c040bf
34
.github/workflows/build.yaml
vendored
34
.github/workflows/build.yaml
vendored
@ -2,11 +2,12 @@ name: build-pipeline
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
- v*
|
||||
paths:
|
||||
- 'pkg/**'
|
||||
- 'frontend/**'
|
||||
- "pkg/**"
|
||||
- "frontend/**"
|
||||
|
||||
jobs:
|
||||
get_filters:
|
||||
@ -17,17 +18,17 @@ jobs:
|
||||
query-service: ${{ steps.filter.outputs.query-service }}
|
||||
flattener: ${{ steps.filter.outputs.flattener }}
|
||||
steps:
|
||||
# For pull requests it's not necessary to checkout the code
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
frontend:
|
||||
- 'frontend/**'
|
||||
query-service:
|
||||
- 'pkg/query-service/**'
|
||||
flattener:
|
||||
- 'pkg/processors/flattener/**'
|
||||
# For pull requests it's not necessary to checkout the code
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
frontend:
|
||||
- 'frontend/**'
|
||||
query-service:
|
||||
- 'pkg/query-service/**'
|
||||
flattener:
|
||||
- 'pkg/processors/flattener/**'
|
||||
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
@ -39,12 +40,11 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: cd frontend && yarn install
|
||||
- name: Run Prettier
|
||||
run: cd frontend && npm run prettify
|
||||
continue-on-error: true
|
||||
- name: Run ESLint
|
||||
run: cd frontend && npm run lint
|
||||
continue-on-error: true
|
||||
- name: TSC
|
||||
run: yarn tsc
|
||||
working-directory: ./frontend
|
||||
- name: Build frontend docker image
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -17,19 +17,20 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.5.0
|
||||
image: signoz/alertmanager:0.6.0
|
||||
volumes:
|
||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
- '--config.file=/prometheus/alertmanager.yml'
|
||||
- '--storage.path=/data'
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
depends_on:
|
||||
- query-service
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.7.3
|
||||
image: signoz/query-service:0.7.4
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
ports:
|
||||
- "8080:8080"
|
||||
@ -48,10 +49,10 @@ services:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
depends_on:
|
||||
- clickhouse
|
||||
- clickhouse
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.7.3
|
||||
image: signoz/frontend:0.7.4
|
||||
depends_on:
|
||||
- query-service
|
||||
ports:
|
||||
|
@ -15,16 +15,17 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.5.0
|
||||
image: signoz/alertmanager:0.6.0
|
||||
volumes:
|
||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
- query-service
|
||||
command:
|
||||
- '--config.file=/prometheus/alertmanager.yml'
|
||||
- '--storage.path=/data'
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.7.3
|
||||
image: signoz/query-service:0.7.4
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@ -44,7 +45,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.7.3
|
||||
image: signoz/frontend:0.7.4
|
||||
container_name: frontend
|
||||
depends_on:
|
||||
- query-service
|
||||
@ -66,7 +67,7 @@ services:
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
# - "55680:55680" # OTLP gRPC legacy port
|
||||
# - "55680:55680" # OTLP gRPC legacy receiver
|
||||
# - "55681:55681" # OTLP HTTP legacy receiver
|
||||
mem_limit: 2000m
|
||||
restart: on-failure
|
||||
@ -93,7 +94,7 @@ services:
|
||||
max-file: "3"
|
||||
command: ["all"]
|
||||
environment:
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
|
||||
load-hotrod:
|
||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||
|
@ -15,19 +15,19 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.5.0
|
||||
image: signoz/alertmanager:0.6.0
|
||||
volumes:
|
||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
- query-service
|
||||
command:
|
||||
- '--config.file=/prometheus/alertmanager.yml'
|
||||
- '--storage.path=/data'
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.7.3
|
||||
image: signoz/query-service:0.7.4
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@ -40,14 +40,13 @@ services:
|
||||
- GODEBUG=netdns=go
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-standalone-amd
|
||||
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.7.3
|
||||
image: signoz/frontend:0.7.4
|
||||
container_name: frontend
|
||||
depends_on:
|
||||
- query-service
|
||||
@ -69,7 +68,7 @@ services:
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
# - "55680:55680" # OTLP gRPC legacy port
|
||||
# - "55680:55680" # OTLP gRPC legacy receiver
|
||||
# - "55681:55681" # OTLP HTTP legacy receiver
|
||||
mem_limit: 2000m
|
||||
restart: on-failure
|
||||
|
@ -84,6 +84,23 @@ module.exports = {
|
||||
tsx: 'never',
|
||||
},
|
||||
],
|
||||
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||
'jsx-a11y/label-has-associated-control': [
|
||||
'error',
|
||||
{
|
||||
required: {
|
||||
some: ['nesting', 'id'],
|
||||
},
|
||||
},
|
||||
],
|
||||
'jsx-a11y/label-has-for': [
|
||||
'error',
|
||||
{
|
||||
required: {
|
||||
some: ['nesting', 'id'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// eslint rules need to remove
|
||||
'no-shadow': 'off',
|
||||
|
4
frontend/.husky/pre-commit
Executable file
4
frontend/.husky/pre-commit
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && yarn lint-staged
|
@ -13,7 +13,9 @@
|
||||
"cypress:run": "cypress run",
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch"
|
||||
"jest:watch": "jest --watch",
|
||||
"postinstall": "yarn husky:configure",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
@ -21,6 +23,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@grafana/data": "^8.4.3",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
@ -102,6 +105,7 @@
|
||||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@testing-library/cypress": "^8.0.0",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/compression-webpack-plugin": "^9.0.0",
|
||||
@ -145,9 +149,9 @@
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "4.3.8",
|
||||
"husky": "^7.0.4",
|
||||
"less-plugin-npm-import": "^2.1.0",
|
||||
"lint-staged": "10.5.3",
|
||||
"lint-staged": "^12.3.7",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"prettier": "2.2.1",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
@ -155,5 +159,10 @@
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"eslint --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,14 @@ import { ErrorResponse } from 'types/api';
|
||||
import { ErrorStatusCode } from 'types/common';
|
||||
|
||||
export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
||||
if (error.response) {
|
||||
const { response, request } = error;
|
||||
if (response) {
|
||||
// client received an error response (5xx, 4xx)
|
||||
// making the error status code as standard Error Status Code
|
||||
const statusCode = error.response.status as ErrorStatusCode;
|
||||
const statusCode = response.status as ErrorStatusCode;
|
||||
|
||||
if (statusCode >= 400 && statusCode < 500) {
|
||||
const { data } = error.response;
|
||||
const { data } = response;
|
||||
|
||||
if (statusCode === 404) {
|
||||
return {
|
||||
@ -35,7 +36,7 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
||||
message: null,
|
||||
};
|
||||
}
|
||||
if (error.request) {
|
||||
if (request) {
|
||||
// client never received a response, or request never left
|
||||
console.error('client never received a response, or request never left');
|
||||
|
||||
@ -51,7 +52,7 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
||||
return {
|
||||
statusCode: 500,
|
||||
payload: null,
|
||||
error: error.toString(),
|
||||
error: String(error),
|
||||
message: null,
|
||||
};
|
||||
}
|
||||
|
29
frontend/src/api/alerts/getTriggered.ts
Normal file
29
frontend/src/api/alerts/getTriggered.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { AxiosAlertManagerInstance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/getTriggered';
|
||||
|
||||
const getTriggered = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const queryParams = convertObjectIntoParams(props);
|
||||
|
||||
const response = await AxiosAlertManagerInstance.get(
|
||||
`/alerts?${queryParams}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTriggered;
|
51
frontend/src/api/channels/createWebhook.ts
Normal file
51
frontend/src/api/channels/createWebhook.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
|
||||
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
|
||||
if (props.username !== '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
},
|
||||
};
|
||||
} else if (props.username === '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'bearer',
|
||||
credentials: props.password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.post('/channels', {
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
50
frontend/src/api/channels/editWebhook.ts
Normal file
50
frontend/src/api/channels/editWebhook.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editWebhook';
|
||||
|
||||
const editWebhook = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
if (props.username !== '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
},
|
||||
};
|
||||
} else if (props.username === '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'bearer',
|
||||
credentials: props.password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.put(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default editWebhook;
|
@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
function Value(props: ValueProps): JSX.Element {
|
||||
const { fillColor } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="78"
|
||||
@ -11,7 +13,7 @@ function Value(props: ValueProps): JSX.Element {
|
||||
>
|
||||
<path
|
||||
d="M15.0215 17.875C14.2285 18.8184 13.2783 19.5771 12.1709 20.1514C11.0771 20.7256 9.87402 21.0127 8.56152 21.0127C6.83887 21.0127 5.33496 20.5889 4.0498 19.7412C2.77832 18.8936 1.79395 17.7041 1.09668 16.1729C0.399414 14.6279 0.0507812 12.9258 0.0507812 11.0664C0.0507812 9.07031 0.426758 7.27246 1.17871 5.67285C1.94434 4.07324 3.02441 2.84961 4.41895 2.00195C5.81348 1.1543 7.44043 0.730469 9.2998 0.730469C12.2529 0.730469 14.5771 1.83789 16.2725 4.05273C17.9814 6.25391 18.8359 9.26172 18.8359 13.0762V14.1836C18.8359 19.9941 17.6875 24.2393 15.3906 26.9189C13.0938 29.585 9.62793 30.9521 4.99316 31.0205H4.25488V27.8213H5.05469C8.18555 27.7666 10.5918 26.9531 12.2734 25.3809C13.9551 23.7949 14.8711 21.293 15.0215 17.875ZM9.17676 17.875C10.4482 17.875 11.6172 17.4854 12.6836 16.7061C13.7637 15.9268 14.5498 14.9629 15.042 13.8145V12.2969C15.042 9.80859 14.502 7.78516 13.4219 6.22656C12.3418 4.66797 10.9746 3.88867 9.32031 3.88867C7.65234 3.88867 6.3125 4.53125 5.30078 5.81641C4.28906 7.08789 3.7832 8.76953 3.7832 10.8613C3.7832 12.8984 4.26855 14.5801 5.23926 15.9062C6.22363 17.2188 7.53613 17.875 9.17676 17.875ZM24.5371 29.0107C24.5371 28.3545 24.7285 27.8076 25.1113 27.3701C25.5078 26.9326 26.0957 26.7139 26.875 26.7139C27.6543 26.7139 28.2422 26.9326 28.6387 27.3701C29.0488 27.8076 29.2539 28.3545 29.2539 29.0107C29.2539 29.6396 29.0488 30.166 28.6387 30.5898C28.2422 31.0137 27.6543 31.2256 26.875 31.2256C26.0957 31.2256 25.5078 31.0137 25.1113 30.5898C24.7285 30.166 24.5371 29.6396 24.5371 29.0107ZM51.1562 20.9717H55.2988V24.0684H51.1562V31H47.3418V24.0684H33.7451V21.833L47.1162 1.14062H51.1562V20.9717ZM38.0518 20.9717H47.3418V6.3291L46.8906 7.14941L38.0518 20.9717ZM73.6123 1.12012V4.33984H72.915C69.9619 4.39453 67.6104 5.26953 65.8604 6.96484C64.1104 8.66016 63.0986 11.0459 62.8252 14.1221C64.3975 12.3174 66.5439 11.415 69.2646 11.415C71.8623 11.415 73.9336 12.3311 75.4785 14.1631C77.0371 15.9951 77.8164 18.3604 77.8164 21.2588C77.8164 24.335 76.9756 26.7959 75.2939 28.6416C73.626 30.4873 71.3838 31.4102 68.5674 31.4102C65.71 31.4102 63.3926 30.3164 61.6152 28.1289C59.8379 25.9277 58.9492 23.0977 58.9492 19.6387V18.1826C58.9492 12.6865 60.1182 8.48926 62.4561 5.59082C64.8076 2.67871 68.3008 1.18848 72.9355 1.12012H73.6123ZM68.6289 14.5732C67.3301 14.5732 66.1338 14.9629 65.04 15.7422C63.9463 16.5215 63.1875 17.499 62.7637 18.6748V20.0693C62.7637 22.5303 63.3174 24.5127 64.4248 26.0166C65.5322 27.5205 66.9131 28.2725 68.5674 28.2725C70.2764 28.2725 71.6162 27.6436 72.5869 26.3857C73.5713 25.1279 74.0635 23.4805 74.0635 21.4434C74.0635 19.3926 73.5645 17.7383 72.5664 16.4805C71.582 15.209 70.2695 14.5732 68.6289 14.5732Z"
|
||||
fill={props.fillColor}
|
||||
fill={fillColor}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -1,5 +1,7 @@
|
||||
import generatePicker from 'antd/es/date-picker/generatePicker';
|
||||
import { Dayjs } from 'dayjs';
|
||||
// included in antd
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
|
||||
|
||||
const DatePicker = generatePicker<Dayjs>(dayjsGenerateConfig);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Chart, ChartType, Plugin } from 'chart.js';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import { get } from 'lodash-es';
|
||||
|
||||
const getOrCreateLegendList = (
|
||||
chart: Chart,
|
||||
@ -40,9 +41,20 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => {
|
||||
}
|
||||
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = chart?.options?.plugins?.legend?.labels?.generateLabels(chart);
|
||||
const items = get(chart, [
|
||||
'options',
|
||||
'plugins',
|
||||
'legend',
|
||||
'labels',
|
||||
'generateLabels',
|
||||
])
|
||||
? get(chart, ['options', 'plugins', 'legend', 'labels', 'generateLabels'])(
|
||||
chart,
|
||||
)
|
||||
: null;
|
||||
|
||||
items?.forEach((item, index) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
items?.forEach((item: Record<any, any>, index: number) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.alignItems = 'center';
|
||||
li.style.cursor = 'pointer';
|
||||
@ -66,8 +78,8 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => {
|
||||
|
||||
// Color box
|
||||
const boxSpan = document.createElement('span');
|
||||
boxSpan.style.background = item.strokeStyle || colors[0];
|
||||
boxSpan.style.borderColor = item?.strokeStyle;
|
||||
boxSpan.style.background = `${item.strokeStyle}` || `${colors[0]}`;
|
||||
boxSpan.style.borderColor = `${item?.strokeStyle}`;
|
||||
boxSpan.style.borderWidth = `${item.lineWidth}px`;
|
||||
boxSpan.style.display = 'inline-block';
|
||||
boxSpan.style.minHeight = '20px';
|
||||
|
@ -79,6 +79,7 @@ function Graph({
|
||||
return 'rgba(231,233,237,0.8)';
|
||||
}, [currentTheme]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const buildChart = useCallback(() => {
|
||||
if (lineChartRef.current !== undefined) {
|
||||
lineChartRef.current.destroy();
|
||||
@ -103,6 +104,21 @@ function Graph({
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label(context) {
|
||||
let label = context.dataset.label || '';
|
||||
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
if (context.parsed.y !== null) {
|
||||
label += getYAxisFormattedValue(context.parsed.y, yAxisUnit);
|
||||
}
|
||||
return label;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
padding: 0,
|
||||
@ -140,8 +156,11 @@ function Graph({
|
||||
},
|
||||
ticks: {
|
||||
// Include a dollar sign in the ticks
|
||||
callback(value, index, ticks) {
|
||||
return getYAxisFormattedValue(value, yAxisUnit);
|
||||
callback(value) {
|
||||
return getYAxisFormattedValue(
|
||||
parseInt(value.toString(), 10),
|
||||
yAxisUnit,
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -201,18 +220,25 @@ interface GraphProps {
|
||||
data: Chart['data'];
|
||||
title?: string;
|
||||
isStacked?: boolean;
|
||||
label?: string[];
|
||||
onClickHandler?: graphOnClickHandler;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
name: string;
|
||||
yAxisUnit?: string;
|
||||
forceReRender?: boolean | null | number;
|
||||
}
|
||||
|
||||
export type graphOnClickHandler = (
|
||||
export type GraphOnClickHandler = (
|
||||
event: ChartEvent,
|
||||
elements: ActiveElement[],
|
||||
chart: Chart,
|
||||
data: ChartData,
|
||||
) => void;
|
||||
|
||||
Graph.defaultProps = {
|
||||
animate: undefined,
|
||||
title: undefined,
|
||||
isStacked: undefined,
|
||||
onClickHandler: undefined,
|
||||
yAxisUnit: undefined,
|
||||
forceReRender: undefined,
|
||||
};
|
||||
export default Graph;
|
||||
|
@ -68,6 +68,37 @@ const TIME_UNITS_CONFIG: IAxisTimeUintConfig[] = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Finds the relevant time unit based on the input time stamps (in ms)
|
||||
*/
|
||||
export const convertTimeRange = (
|
||||
start: number,
|
||||
end: number,
|
||||
): IAxisTimeConfig => {
|
||||
const MIN_INTERVALS = 6;
|
||||
const range = end - start;
|
||||
let relevantTimeUnit = TIME_UNITS_CONFIG[1];
|
||||
let stepSize = 1;
|
||||
try {
|
||||
for (let idx = TIME_UNITS_CONFIG.length - 1; idx >= 0; idx -= 1) {
|
||||
const timeUnit = TIME_UNITS_CONFIG[idx];
|
||||
const units = range * timeUnit.multiplier;
|
||||
const steps = units / MIN_INTERVALS;
|
||||
if (steps >= 1) {
|
||||
relevantTimeUnit = timeUnit;
|
||||
stepSize = steps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return {
|
||||
unitName: relevantTimeUnit.unitName,
|
||||
stepSize: Math.floor(stepSize) || 1,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepts Chart.js data's data-structure and returns the relevant time unit for the axis based on the range of the data.
|
||||
*/
|
||||
@ -77,10 +108,18 @@ export const useXAxisTimeUnit = (data: Chart['data']): IAxisTimeConfig => {
|
||||
try {
|
||||
let minTime = Number.POSITIVE_INFINITY;
|
||||
let maxTime = Number.NEGATIVE_INFINITY;
|
||||
data?.labels?.forEach((timeStamp: string | number): void => {
|
||||
if (typeof timeStamp === 'string') timeStamp = Date.parse(timeStamp);
|
||||
minTime = Math.min(timeStamp, minTime);
|
||||
maxTime = Math.max(timeStamp, maxTime);
|
||||
data?.labels?.forEach((timeStamp: unknown): void => {
|
||||
const getTimeStamp = (time: string | number): Date | number | string => {
|
||||
if (typeof timeStamp === 'string') {
|
||||
return Date.parse(timeStamp);
|
||||
}
|
||||
|
||||
return time;
|
||||
};
|
||||
const time = getTimeStamp(timeStamp as string | number);
|
||||
|
||||
minTime = Math.min(parseInt(time.toString(), 10), minTime);
|
||||
maxTime = Math.max(parseInt(time.toString(), 10), maxTime);
|
||||
});
|
||||
|
||||
localTime = {
|
||||
@ -113,34 +152,3 @@ export const useXAxisTimeUnit = (data: Chart['data']): IAxisTimeConfig => {
|
||||
|
||||
return convertTimeRange(minTime, maxTime);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the relevant time unit based on the input time stamps (in ms)
|
||||
*/
|
||||
export const convertTimeRange = (
|
||||
start: number,
|
||||
end: number,
|
||||
): IAxisTimeConfig => {
|
||||
const MIN_INTERVALS = 6;
|
||||
const range = end - start;
|
||||
let relevantTimeUnit = TIME_UNITS_CONFIG[1];
|
||||
let stepSize = 1;
|
||||
try {
|
||||
for (let idx = TIME_UNITS_CONFIG.length - 1; idx >= 0; idx--) {
|
||||
const timeUnit = TIME_UNITS_CONFIG[idx];
|
||||
const units = range * timeUnit.multiplier;
|
||||
const steps = units / MIN_INTERVALS;
|
||||
if (steps >= 1) {
|
||||
relevantTimeUnit = timeUnit;
|
||||
stepSize = steps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return {
|
||||
unitName: relevantTimeUnit.unitName,
|
||||
stepSize: Math.floor(stepSize) || 1,
|
||||
};
|
||||
};
|
||||
|
@ -3,7 +3,6 @@ import { formattedValueToString, getValueFormat } from '@grafana/data';
|
||||
export const getYAxisFormattedValue = (
|
||||
value: number,
|
||||
format: string,
|
||||
decimal?: number,
|
||||
): string => {
|
||||
try {
|
||||
return formattedValueToString(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Form, Input, InputProps } from 'antd';
|
||||
import { Form, Input, InputProps, InputRef } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
function InputComponent({
|
||||
@ -22,11 +22,12 @@ function InputComponent({
|
||||
type={type}
|
||||
onChange={onChangeHandler}
|
||||
value={value}
|
||||
ref={ref}
|
||||
ref={ref as React.Ref<InputRef>}
|
||||
size={size}
|
||||
addonBefore={addonBefore}
|
||||
onBlur={onBlurHandler}
|
||||
onPressEnter={onPressEnterHandler}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
</Form.Item>
|
||||
@ -38,7 +39,7 @@ interface InputComponentProps extends InputProps {
|
||||
type?: InputProps['type'];
|
||||
onChangeHandler?: React.ChangeEventHandler<HTMLInputElement>;
|
||||
placeholder?: InputProps['placeholder'];
|
||||
ref?: React.LegacyRef<Input>;
|
||||
ref?: React.LegacyRef<InputRef>;
|
||||
size?: InputProps['size'];
|
||||
onBlurHandler?: React.FocusEventHandler<HTMLInputElement>;
|
||||
onPressEnterHandler?: React.KeyboardEventHandler<HTMLInputElement>;
|
||||
@ -47,4 +48,17 @@ interface InputComponentProps extends InputProps {
|
||||
addonBefore?: React.ReactNode;
|
||||
}
|
||||
|
||||
InputComponent.defaultProps = {
|
||||
type: undefined,
|
||||
onChangeHandler: undefined,
|
||||
placeholder: undefined,
|
||||
ref: undefined,
|
||||
size: undefined,
|
||||
onBlurHandler: undefined,
|
||||
onPressEnterHandler: undefined,
|
||||
label: undefined,
|
||||
labelOnTop: undefined,
|
||||
addonBefore: undefined,
|
||||
};
|
||||
|
||||
export default InputComponent;
|
||||
|
@ -28,4 +28,9 @@ interface ModalProps {
|
||||
children: ReactElement;
|
||||
}
|
||||
|
||||
CustomModal.defaultProps = {
|
||||
closable: undefined,
|
||||
footer: undefined,
|
||||
};
|
||||
|
||||
export default CustomModal;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { expect } from '@jest/globals';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
@ -10,8 +10,10 @@ function RouteTab({
|
||||
onChangeHandler,
|
||||
...rest
|
||||
}: RouteTabProps & TabsProps): JSX.Element {
|
||||
const onChange = (activeRoute: string) => {
|
||||
onChangeHandler && onChangeHandler();
|
||||
const onChange = (activeRoute: string): void => {
|
||||
if (onChangeHandler) {
|
||||
onChangeHandler();
|
||||
}
|
||||
|
||||
const selectedRoute = routes.find((e) => e.name === activeRoute);
|
||||
|
||||
@ -25,6 +27,7 @@ function RouteTab({
|
||||
onChange={onChange}
|
||||
destroyInactiveTabPane
|
||||
activeKey={activeKey}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...rest}
|
||||
>
|
||||
{routes.map(
|
||||
@ -48,4 +51,8 @@ interface RouteTabProps {
|
||||
onChangeHandler?: VoidFunction;
|
||||
}
|
||||
|
||||
RouteTab.defaultProps = {
|
||||
onChangeHandler: undefined,
|
||||
};
|
||||
|
||||
export default RouteTab;
|
||||
|
@ -17,5 +17,10 @@ interface SpinnerProps {
|
||||
tip?: SpinProps['tip'];
|
||||
height?: React.CSSProperties['height'];
|
||||
}
|
||||
Spinner.defaultProps = {
|
||||
size: undefined,
|
||||
tip: undefined,
|
||||
height: undefined,
|
||||
};
|
||||
|
||||
export default Spinner;
|
||||
|
@ -6,8 +6,8 @@ import styled, { FlattenSimpleInterpolation } from 'styled-components';
|
||||
|
||||
import { IStyledClass } from './types';
|
||||
|
||||
const styledClass = (props: IStyledClass): FlattenSimpleInterpolation =>
|
||||
props.styledclass;
|
||||
const styledClass = (props: IStyledClass): FlattenSimpleInterpolation | null =>
|
||||
props.styledclass || null;
|
||||
|
||||
type TStyledCol = AntD.ColProps & IStyledClass;
|
||||
const StyledCol = styled(AntD.Col)<TStyledCol>`
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { css, FlattenSimpleInterpolation } from 'styled-components';
|
||||
|
||||
const cssProprty = (key: string, value): FlattenSimpleInterpolation =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const cssProperty = (key: any, value: any): FlattenSimpleInterpolation =>
|
||||
key &&
|
||||
value &&
|
||||
css`
|
||||
@ -15,8 +16,8 @@ export const Flex = ({
|
||||
flexDirection,
|
||||
flex,
|
||||
}: IFlexProps): FlattenSimpleInterpolation => css`
|
||||
${cssProprty('flex-direction', flexDirection)}
|
||||
${cssProprty('flex', flex)}
|
||||
${cssProperty('flex-direction', flexDirection)}
|
||||
${cssProperty('flex', flex)}
|
||||
`;
|
||||
|
||||
interface IDisplayProps {
|
||||
@ -25,7 +26,7 @@ interface IDisplayProps {
|
||||
export const Display = ({
|
||||
display,
|
||||
}: IDisplayProps): FlattenSimpleInterpolation => css`
|
||||
${cssProprty('display', display)}
|
||||
${cssProperty('display', display)}
|
||||
`;
|
||||
|
||||
interface ISpacingProps {
|
||||
@ -36,6 +37,6 @@ export const Spacing = ({
|
||||
margin,
|
||||
padding,
|
||||
}: ISpacingProps): FlattenSimpleInterpolation => css`
|
||||
${cssProprty('margin', margin)}
|
||||
${cssProprty('padding', padding)}
|
||||
${cssProperty('margin', margin)}
|
||||
${cssProperty('padding', padding)}
|
||||
`;
|
||||
|
@ -1,11 +1,12 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import { QuestionCircleFilled } from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
function TextToolTip({ text, url }: TextToolTipProps) {
|
||||
function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
||||
return (
|
||||
<Tooltip
|
||||
overlay={() => {
|
||||
overlay={(): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
{`${text} `}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Button, Dropdown, Menu, Typography } from 'antd';
|
||||
import timeItems, {
|
||||
import TimeItems, {
|
||||
timePreferance,
|
||||
timePreferenceType,
|
||||
} from 'container/NewWidget/RightContainer/timeItems';
|
||||
@ -13,7 +13,7 @@ function TimePreference({
|
||||
}: TimePreferenceDropDownProps): JSX.Element {
|
||||
const timeMenuItemOnChangeHandler = useCallback(
|
||||
(event: TimeMenuItemOnChangeHandlerEvent) => {
|
||||
const selectedTime = timeItems.find((e) => e.enum === event.key);
|
||||
const selectedTime = TimeItems.find((e) => e.enum === event.key);
|
||||
if (selectedTime !== undefined) {
|
||||
setSelectedTime(selectedTime);
|
||||
}
|
||||
@ -26,7 +26,7 @@ function TimePreference({
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
{timeItems.map((item) => (
|
||||
{TimeItems.map((item) => (
|
||||
<Menu.Item onClick={timeMenuItemOnChangeHandler} key={item.enum}>
|
||||
<Typography>{item.name}</Typography>
|
||||
</Menu.Item>
|
||||
|
@ -6,4 +6,4 @@ export const AUTH0_REDIRECT_PATH = '/redirect';
|
||||
|
||||
export const DEFAULT_AUTH0_APP_REDIRECTION_PATH = ROUTES.APPLICATION;
|
||||
|
||||
export const IS_SIDEBAR_COLLAPSED = 'isSideBarCollapsed'
|
||||
export const IS_SIDEBAR_COLLAPSED = 'isSideBarCollapsed';
|
||||
|
@ -1,3 +1,3 @@
|
||||
export enum LOCAL_STORAGE {
|
||||
export enum LOCALSTORAGE {
|
||||
METRICS_TIME_IN_DURATION = 'metricsTimeDurations',
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export enum METRICS_PAGE_QUERY_PARAM {
|
||||
interval = 'interval',
|
||||
startTime = 'startTime',
|
||||
|
@ -4,7 +4,7 @@ import { ColumnsType } from 'antd/lib/table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { generatePath } from 'react-router';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { Channels, PayloadProps } from 'types/api/channels/getAll';
|
||||
|
||||
import Delete from './Delete';
|
||||
|
@ -30,7 +30,8 @@ function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: error.toString() || 'Something went wrong',
|
||||
description:
|
||||
error instanceof Error ? error.toString() : 'Something went wrong',
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
|
@ -4,17 +4,19 @@ import SideNav from 'container/SideNav';
|
||||
import history from 'lib/history';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { Content, Layout } from './styles';
|
||||
|
||||
const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||
function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [isSignUpPage, setIsSignUpPage] = useState(
|
||||
ROUTES.SIGN_UP === location.pathname,
|
||||
);
|
||||
const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname);
|
||||
|
||||
const { children } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn) {
|
||||
@ -36,7 +38,7 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: ReactNode;
|
||||
|
@ -1,10 +1,22 @@
|
||||
export interface SlackChannel {
|
||||
send_resolved: boolean;
|
||||
api_url: string;
|
||||
channel: string;
|
||||
title: string;
|
||||
text: string;
|
||||
export interface Channel {
|
||||
send_resolved?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type ChannelType = 'slack' | 'email';
|
||||
export interface SlackChannel extends Channel {
|
||||
api_url?: string;
|
||||
channel?: string;
|
||||
title?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface WebhookChannel extends Channel {
|
||||
api_url?: string;
|
||||
// basic auth
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export type ChannelType = 'slack' | 'email' | 'webhook';
|
||||
export const SlackType: ChannelType = 'slack';
|
||||
export const WebhookType: ChannelType = 'webhook';
|
||||
|
@ -1,17 +1,26 @@
|
||||
import { Form, notification } from 'antd';
|
||||
import createSlackApi from 'api/channels/createSlack';
|
||||
import createWebhookApi from 'api/channels/createWebhook';
|
||||
import ROUTES from 'constants/routes';
|
||||
import FormAlertChannels from 'container/FormAlertChannels';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { ChannelType, SlackChannel } from './config';
|
||||
import {
|
||||
ChannelType,
|
||||
SlackChannel,
|
||||
SlackType,
|
||||
WebhookChannel,
|
||||
WebhookType,
|
||||
} from './config';
|
||||
|
||||
function CreateAlertChannels({
|
||||
preType = 'slack',
|
||||
}: CreateAlertChannelsProps): JSX.Element {
|
||||
const [formInstance] = Form.useForm();
|
||||
const [selectedConfig, setSelectedConfig] = useState<Partial<SlackChannel>>({
|
||||
const [selectedConfig, setSelectedConfig] = useState<
|
||||
Partial<SlackChannel & WebhookChannel>
|
||||
>({
|
||||
text: ` {{ range .Alerts -}}
|
||||
*Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||
|
||||
@ -73,17 +82,93 @@ function CreateAlertChannels({
|
||||
}
|
||||
setSavingState(false);
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description:
|
||||
'An unexpected error occurred while creating this channel, please try again',
|
||||
});
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [notifications, selectedConfig]);
|
||||
|
||||
const onWebhookHandler = useCallback(async () => {
|
||||
// initial api request without auth params
|
||||
let request: WebhookChannel = {
|
||||
api_url: selectedConfig?.api_url || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
};
|
||||
|
||||
setSavingState(true);
|
||||
|
||||
try {
|
||||
if (selectedConfig?.username !== '' || selectedConfig?.password !== '') {
|
||||
if (selectedConfig?.username !== '') {
|
||||
// if username is not null then password must be passed
|
||||
if (selectedConfig?.password !== '') {
|
||||
request = {
|
||||
...request,
|
||||
username: selectedConfig.username,
|
||||
password: selectedConfig.password,
|
||||
};
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: 'A Password must be provided with user name',
|
||||
});
|
||||
}
|
||||
} else if (selectedConfig?.password !== '') {
|
||||
// only password entered, set bearer token
|
||||
request = {
|
||||
...request,
|
||||
username: '',
|
||||
password: selectedConfig.password,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const response = await createWebhookApi(request);
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: 'Successfully created the channel',
|
||||
});
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: response.error || 'Error while creating the channel',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description:
|
||||
'An unexpected error occurred while creating this channel, please try again',
|
||||
});
|
||||
}
|
||||
setSavingState(false);
|
||||
}, [notifications, selectedConfig]);
|
||||
|
||||
const onSaveHandler = useCallback(
|
||||
async (value: ChannelType) => {
|
||||
if (value == 'slack') {
|
||||
onSlackHandler();
|
||||
switch (value) {
|
||||
case SlackType:
|
||||
onSlackHandler();
|
||||
break;
|
||||
case WebhookType:
|
||||
onWebhookHandler();
|
||||
break;
|
||||
default:
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: 'channel type selected is invalid',
|
||||
});
|
||||
}
|
||||
},
|
||||
[onSlackHandler],
|
||||
[onSlackHandler, onWebhookHandler, notifications],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -108,7 +193,7 @@ function CreateAlertChannels({
|
||||
}
|
||||
|
||||
interface CreateAlertChannelsProps {
|
||||
preType?: ChannelType;
|
||||
preType: ChannelType;
|
||||
}
|
||||
|
||||
export default CreateAlertChannels;
|
||||
|
@ -1,28 +1,35 @@
|
||||
import { Form, notification } from 'antd';
|
||||
import editSlackApi from 'api/channels/editSlack';
|
||||
import editWebhookApi from 'api/channels/editWebhook';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
SlackChannel,
|
||||
SlackType,
|
||||
WebhookChannel,
|
||||
WebhookType,
|
||||
} from 'container/CreateAlertChannels/config';
|
||||
import FormAlertChannels from 'container/FormAlertChannels';
|
||||
import history from 'lib/history';
|
||||
import { Store } from 'rc-field-form/lib/interface';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
function EditAlertChannels({
|
||||
initialValue,
|
||||
}: EditAlertChannelsProps): JSX.Element {
|
||||
const [formInstance] = Form.useForm();
|
||||
const [selectedConfig, setSelectedConfig] = useState<Partial<SlackChannel>>({
|
||||
const [selectedConfig, setSelectedConfig] = useState<
|
||||
Partial<SlackChannel & WebhookChannel>
|
||||
>({
|
||||
...initialValue,
|
||||
});
|
||||
const [savingState, setSavingState] = useState<boolean>(false);
|
||||
const [notifications, NotificationElement] = notification.useNotification();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const [type, setType] = useState<ChannelType>('slack');
|
||||
const [type, setType] = useState<ChannelType>(
|
||||
initialValue?.type ? (initialValue.type as ChannelType) : SlackType,
|
||||
);
|
||||
|
||||
const onTypeChangeHandler = useCallback((value: string) => {
|
||||
setType(value as ChannelType);
|
||||
@ -58,13 +65,62 @@ function EditAlertChannels({
|
||||
setSavingState(false);
|
||||
}, [selectedConfig, notifications, id]);
|
||||
|
||||
const onWebhookEditHandler = useCallback(async () => {
|
||||
setSavingState(true);
|
||||
const { name, username, password } = selectedConfig;
|
||||
|
||||
const showError = (msg: string): void => {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: msg,
|
||||
});
|
||||
};
|
||||
|
||||
if (selectedConfig?.api_url === '') {
|
||||
showError('Webhook URL is mandatory');
|
||||
setSavingState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (username && (!password || password === '')) {
|
||||
showError('Please enter a password');
|
||||
setSavingState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await editWebhookApi({
|
||||
api_url: selectedConfig?.api_url || '',
|
||||
name: name || '',
|
||||
send_resolved: true,
|
||||
username,
|
||||
password,
|
||||
id,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: 'Channels Edited Successfully',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
} else {
|
||||
showError(response.error || 'error while updating the Channels');
|
||||
}
|
||||
setSavingState(false);
|
||||
}, [selectedConfig, notifications, id]);
|
||||
|
||||
const onSaveHandler = useCallback(
|
||||
(value: ChannelType) => {
|
||||
if (value === 'slack') {
|
||||
if (value === SlackType) {
|
||||
onSlackEditHandler();
|
||||
} else if (value === WebhookType) {
|
||||
onWebhookEditHandler();
|
||||
}
|
||||
},
|
||||
[onSlackEditHandler],
|
||||
[onSlackEditHandler, onWebhookEditHandler],
|
||||
);
|
||||
|
||||
const onTestHandler = useCallback(() => {
|
||||
@ -91,7 +147,9 @@ function EditAlertChannels({
|
||||
}
|
||||
|
||||
interface EditAlertChannelsProps {
|
||||
initialValue: Store;
|
||||
initialValue: {
|
||||
[x: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export default EditAlertChannels;
|
||||
|
@ -0,0 +1,59 @@
|
||||
import { Input } from 'antd';
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
import React from 'react';
|
||||
|
||||
import { WebhookChannel } from '../../CreateAlertChannels/config';
|
||||
|
||||
function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<FormItem name="api_url" label="Webhook URL">
|
||||
<Input
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
...value,
|
||||
api_url: event.target.value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="username"
|
||||
label="User Name (optional)"
|
||||
help="Leave empty for bearer auth or when authentication is not necessary."
|
||||
>
|
||||
<Input
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
...value,
|
||||
username: event.target.value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="password"
|
||||
label="Password (optional)"
|
||||
help="Specify a password or bearer token"
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
...value,
|
||||
password: event.target.value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface WebhookProps {
|
||||
setSelectedConfig: React.Dispatch<
|
||||
React.SetStateAction<Partial<WebhookChannel>>
|
||||
>;
|
||||
}
|
||||
|
||||
export default WebhookSettings;
|
@ -1,15 +1,18 @@
|
||||
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
SlackChannel,
|
||||
SlackType,
|
||||
WebhookType,
|
||||
} from 'container/CreateAlertChannels/config';
|
||||
import history from 'lib/history';
|
||||
import { Store } from 'rc-field-form/lib/interface';
|
||||
import React from 'react';
|
||||
|
||||
import SlackSettings from './Settings/Slack';
|
||||
import WebhookSettings from './Settings/Webhook';
|
||||
import { Button } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
@ -28,6 +31,16 @@ function FormAlertChannels({
|
||||
initialValue,
|
||||
nameDisable = false,
|
||||
}: FormAlertChannelsProps): JSX.Element {
|
||||
const renderSettings = (): React.ReactElement | null => {
|
||||
switch (type) {
|
||||
case SlackType:
|
||||
return <SlackSettings setSelectedConfig={setSelectedConfig} />;
|
||||
case WebhookType:
|
||||
return <WebhookSettings setSelectedConfig={setSelectedConfig} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
@ -52,14 +65,13 @@ function FormAlertChannels({
|
||||
<Option value="slack" key="slack">
|
||||
Slack
|
||||
</Option>
|
||||
<Option value="webhook" key="webhook">
|
||||
Webhook
|
||||
</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
{type === 'slack' && (
|
||||
<SlackSettings setSelectedConfig={setSelectedConfig} />
|
||||
)}
|
||||
</FormItem>
|
||||
<FormItem>{renderSettings()}</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button
|
||||
@ -89,7 +101,6 @@ interface FormAlertChannelsProps {
|
||||
type: ChannelType;
|
||||
setSelectedConfig: React.Dispatch<React.SetStateAction<Partial<SlackChannel>>>;
|
||||
onTypeChangeHandler: (value: ChannelType) => void;
|
||||
onTestHandler: () => void;
|
||||
onSaveHandler: (props: ChannelType) => void;
|
||||
savingState: boolean;
|
||||
NotificationElement: React.ReactElement<
|
||||
@ -101,4 +112,8 @@ interface FormAlertChannelsProps {
|
||||
nameDisable?: boolean;
|
||||
}
|
||||
|
||||
FormAlertChannels.defaultProps = {
|
||||
nameDisable: undefined,
|
||||
};
|
||||
|
||||
export default FormAlertChannels;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Tooltip, Typography } from 'antd';
|
||||
import {
|
||||
IIntervalUnit,
|
||||
resolveTimeFromInterval,
|
||||
@ -13,21 +12,29 @@ interface SpanLengthProps {
|
||||
width: string;
|
||||
leftOffset: string;
|
||||
bgColor: string;
|
||||
toolTipText: string;
|
||||
id: string;
|
||||
inMsCount: number;
|
||||
intervalUnit: IIntervalUnit;
|
||||
}
|
||||
|
||||
function SpanLength(props: SpanLengthProps): JSX.Element {
|
||||
const { width, leftOffset, bgColor, intervalUnit } = props;
|
||||
const { width, leftOffset, bgColor, intervalUnit, inMsCount } = props;
|
||||
const { isDarkMode } = useThemeMode();
|
||||
return (
|
||||
<SpanWrapper>
|
||||
<SpanLine leftOffset={leftOffset} isDarkMode={isDarkMode} />
|
||||
<SpanBorder bgColor={bgColor} leftOffset={leftOffset} width={width} />
|
||||
<SpanText leftOffset={leftOffset} isDarkMode={isDarkMode}>{`${toFixed(
|
||||
resolveTimeFromInterval(props.inMsCount, intervalUnit),
|
||||
<SpanLine
|
||||
isDarkMode={isDarkMode}
|
||||
bgColor={bgColor}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
/>
|
||||
<SpanBorder
|
||||
isDarkMode={isDarkMode}
|
||||
bgColor={bgColor}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
/>
|
||||
<SpanText isDarkMode={isDarkMode} leftOffset={leftOffset}>{`${toFixed(
|
||||
resolveTimeFromInterval(inMsCount, intervalUnit),
|
||||
2,
|
||||
)} ${intervalUnit.name}`}</SpanText>
|
||||
</SpanWrapper>
|
||||
|
@ -9,19 +9,19 @@ interface Props {
|
||||
}
|
||||
|
||||
export const SpanLine = styled.div<Props>`
|
||||
width: ${({ leftOffset }) => `${leftOffset}%`};
|
||||
width: ${({ leftOffset }): string => `${leftOffset}%`};
|
||||
height: 0px;
|
||||
border-bottom: 0.1px solid
|
||||
${({ isDarkMode }) => (isDarkMode ? '#303030' : '#c0c0c0')};
|
||||
${({ isDarkMode }): string => (isDarkMode ? '#303030' : '#c0c0c0')};
|
||||
top: 50%;
|
||||
position: absolute;
|
||||
`;
|
||||
export const SpanBorder = styled.div<Props>`
|
||||
background: ${({ bgColor }) => bgColor};
|
||||
background: ${({ bgColor }): string => bgColor};
|
||||
border-radius: 5px;
|
||||
height: 0.625rem;
|
||||
width: ${({ width }) => `${width}%`};
|
||||
left: ${({ leftOffset }) => `${leftOffset}%`};
|
||||
width: ${({ width }): string => `${width}%`};
|
||||
left: ${({ leftOffset }): string => `${leftOffset}%`};
|
||||
top: 35%;
|
||||
position: absolute;
|
||||
`;
|
||||
@ -45,13 +45,16 @@ export const SpanWrapper = styled.div`
|
||||
z-index: 0;
|
||||
} */
|
||||
`;
|
||||
interface SpanTextProps extends Pick<Props, 'leftOffset'> {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const SpanText = styled(Typography)<Pick<Props, 'leftOffset'>>`
|
||||
export const SpanText = styled(Typography)<SpanTextProps>`
|
||||
&&& {
|
||||
left: ${({ leftOffset }) => `${leftOffset}%`};
|
||||
left: ${({ leftOffset }): string => `${leftOffset}%`};
|
||||
top: 65%;
|
||||
position: absolute;
|
||||
color: ${({ isDarkMode }) => (isDarkMode ? '##ACACAC' : '#666')};
|
||||
color: ${({ isDarkMode }): string => (isDarkMode ? '##ACACAC' : '#666')};
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
`;
|
||||
|
@ -1,18 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Container,
|
||||
Service,
|
||||
Span,
|
||||
SpanConnector,
|
||||
SpanName,
|
||||
SpanWrapper,
|
||||
} from './styles';
|
||||
import { Container, Service, Span, SpanWrapper } from './styles';
|
||||
|
||||
function SpanNameComponent({
|
||||
name,
|
||||
serviceName,
|
||||
}: SpanNameComponent): JSX.Element {
|
||||
}: SpanNameComponentProps): JSX.Element {
|
||||
return (
|
||||
<Container title={`${name} ${serviceName}`}>
|
||||
<SpanWrapper>
|
||||
@ -23,7 +16,7 @@ function SpanNameComponent({
|
||||
);
|
||||
}
|
||||
|
||||
interface SpanNameComponent {
|
||||
interface SpanNameComponentProps {
|
||||
name: string;
|
||||
serviceName: string;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { IIntervalUnit } from 'container/TraceDetail/utils';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { pushDStree } from 'store/actions';
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
|
||||
import { ITraceMetaData } from '..';
|
||||
import SpanLength from '../SpanLength';
|
||||
@ -38,6 +38,7 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
activeSpanPath,
|
||||
isExpandAll,
|
||||
intervalUnit,
|
||||
children,
|
||||
} = props;
|
||||
|
||||
const { isDarkMode } = useThemeMode();
|
||||
@ -52,7 +53,7 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
} else if (!isOpen) {
|
||||
setOpen(activeSpanPath[level] === id);
|
||||
}
|
||||
}, [activeSpanPath, isOpen]);
|
||||
}, [activeSpanPath, isOpen, id, level]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpandAll) {
|
||||
@ -60,9 +61,9 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
} else {
|
||||
setOpen(activeSpanPath[level] === id);
|
||||
}
|
||||
}, [isExpandAll]);
|
||||
}, [isExpandAll, activeSpanPath, id, level]);
|
||||
|
||||
const isOnlyChild = props.children.length === 1;
|
||||
const isOnlyChild = children.length === 1;
|
||||
const [top, setTop] = useState<number>(0);
|
||||
|
||||
const ref = useRef<HTMLUListElement>(null);
|
||||
@ -75,25 +76,27 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
}, [activeSelectedId]);
|
||||
}, [activeSelectedId, id]);
|
||||
|
||||
const onMouseEnterHandler = () => {
|
||||
setActiveHoverId(props.id);
|
||||
const onMouseEnterHandler = (): void => {
|
||||
setActiveHoverId(id);
|
||||
if (ref.current) {
|
||||
const { top } = getTopLeftFromBody(ref.current);
|
||||
setTop(top);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseLeaveHandler = () => {
|
||||
const onMouseLeaveHandler = (): void => {
|
||||
setActiveHoverId('');
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
const onClick = (): void => {
|
||||
setActiveSelectedId(id);
|
||||
};
|
||||
|
||||
const onClickTreeExpansion = (event) => {
|
||||
const onClickTreeExpansion: React.MouseEventHandler<HTMLDivElement> = (
|
||||
event,
|
||||
): void => {
|
||||
event.stopPropagation();
|
||||
setOpen((state) => {
|
||||
localTreeExpandInteraction.current = !isOpen;
|
||||
@ -113,6 +116,7 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
onMouseLeave={onMouseLeaveHandler}
|
||||
isOnlyChild={isOnlyChild}
|
||||
ref={ref}
|
||||
isDarkMode={isDarkMode}
|
||||
>
|
||||
<HoverCard
|
||||
top={top}
|
||||
@ -126,7 +130,11 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
<StyledRow styledclass={[styles.flexNoWrap]}>
|
||||
<Col>
|
||||
{totalSpans !== 1 && (
|
||||
<CardComponent isDarkMode={isDarkMode} onClick={onClickTreeExpansion}>
|
||||
<CardComponent
|
||||
isOnlyChild={isOnlyChild}
|
||||
isDarkMode={isDarkMode}
|
||||
onClick={onClickTreeExpansion}
|
||||
>
|
||||
{totalSpans}
|
||||
<CaretContainer>
|
||||
{isOpen ? <CaretDownFilled /> : <CaretRightFilled />}
|
||||
@ -144,7 +152,6 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
leftOffset={nodeLeftOffset.toString()}
|
||||
width={width.toString()}
|
||||
bgColor={serviceColour}
|
||||
id={id}
|
||||
inMsCount={inMsCount / 1e6}
|
||||
intervalUnit={intervalUnit}
|
||||
/>
|
||||
@ -153,11 +160,12 @@ function Trace(props: TraceProps): JSX.Element {
|
||||
|
||||
{isOpen && (
|
||||
<>
|
||||
{props.children.map((child) => (
|
||||
{children.map((child) => (
|
||||
<Trace
|
||||
key={child.id}
|
||||
activeHoverId={props.activeHoverId}
|
||||
setActiveHoverId={props.setActiveHoverId}
|
||||
activeHoverId={activeHoverId}
|
||||
setActiveHoverId={setActiveHoverId}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...child}
|
||||
globalSpread={globalSpread}
|
||||
globalStart={globalStart}
|
||||
@ -180,7 +188,7 @@ interface ITraceGlobal {
|
||||
globalStart: ITraceMetaData['globalStart'];
|
||||
}
|
||||
|
||||
interface TraceProps extends pushDStree, ITraceGlobal {
|
||||
interface TraceProps extends ITraceTree, ITraceGlobal {
|
||||
activeHoverId: string;
|
||||
setActiveHoverId: React.Dispatch<React.SetStateAction<string>>;
|
||||
setActiveSelectedId: React.Dispatch<React.SetStateAction<string>>;
|
||||
|
@ -1,4 +1,8 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import styled, {
|
||||
css,
|
||||
DefaultTheme,
|
||||
ThemedCssFunction,
|
||||
} from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
isOnlyChild: boolean;
|
||||
@ -13,9 +17,10 @@ export const Wrapper = styled.ul<Props>`
|
||||
z-index: 1;
|
||||
|
||||
ul {
|
||||
border-left: ${({ isOnlyChild }) => isOnlyChild && 'none'} !important;
|
||||
border-left: ${({ isOnlyChild }): StyledCSS =>
|
||||
isOnlyChild && 'none'} !important;
|
||||
|
||||
${({ isOnlyChild }) =>
|
||||
${({ isOnlyChild }): StyledCSS =>
|
||||
isOnlyChild &&
|
||||
css`
|
||||
&:before {
|
||||
@ -37,15 +42,27 @@ export const CardContainer = styled.li`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const CardComponent = styled.div`
|
||||
border: 1px solid ${({ isDarkMode }) => (isDarkMode ? '#434343' : '#333')};
|
||||
interface Props {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export type StyledCSS =
|
||||
| ReturnType<ThemedCssFunction<DefaultTheme>>
|
||||
| string
|
||||
| false
|
||||
| undefined;
|
||||
|
||||
export const CardComponent = styled.div<Props>`
|
||||
border: 1px solid
|
||||
${({ isDarkMode }): StyledCSS => (isDarkMode ? '#434343' : '#333')};
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1px 8px;
|
||||
background: ${({ isDarkMode }) => (isDarkMode ? '#1d1d1d' : '#ddd')};
|
||||
background: ${({ isDarkMode }): StyledCSS =>
|
||||
isDarkMode ? '#1d1d1d' : '#ddd'};
|
||||
height: 22px;
|
||||
`;
|
||||
|
||||
@ -61,13 +78,15 @@ interface HoverCardProps {
|
||||
}
|
||||
|
||||
export const HoverCard = styled.div<HoverCardProps>`
|
||||
display: ${({ isSelected, isHovered }) =>
|
||||
display: ${({ isSelected, isHovered }): string =>
|
||||
isSelected || isHovered ? 'block' : 'none'};
|
||||
width: 200%;
|
||||
background-color: ${({ isHovered, isDarkMode }) =>
|
||||
isHovered && (isDarkMode ? '#262626' : '#ddd')};
|
||||
background-color: ${({ isSelected, isDarkMode }) =>
|
||||
isSelected && (isDarkMode ? '#4f4f4f' : '#bbb')};
|
||||
background-color: ${({ isHovered, isDarkMode }): string => {
|
||||
if (isHovered) {
|
||||
return isDarkMode ? '#262626' : '#ddd';
|
||||
}
|
||||
return isDarkMode ? '#4f4f4f' : '#bbb';
|
||||
}};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
|
@ -26,13 +26,13 @@ function GanttChart(props: GanttChartProps): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
setActiveSpanPath(getSpanPath(data, spanId));
|
||||
}, [spanId]);
|
||||
}, [spanId, data]);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveSpanPath(getSpanPath(data, activeSelectedId));
|
||||
}, [activeSelectedId]);
|
||||
}, [activeSelectedId, data]);
|
||||
|
||||
const handleCollapse = () => {
|
||||
const handleCollapse = (): void => {
|
||||
setIsExpandAll((prev) => !prev);
|
||||
};
|
||||
return (
|
||||
@ -50,6 +50,7 @@ function GanttChart(props: GanttChartProps): JSX.Element {
|
||||
activeSpanPath={activeSpanPath}
|
||||
setActiveHoverId={setActiveHoverId}
|
||||
key={data.id}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...{
|
||||
...data,
|
||||
globalSpread,
|
||||
|
@ -1,24 +1,33 @@
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
|
||||
export const getMetaDataFromSpanTree = (treeData: ITraceTree) => {
|
||||
interface GetTraceMetaData {
|
||||
globalStart: number;
|
||||
globalEnd: number;
|
||||
spread: number;
|
||||
totalSpans: number;
|
||||
levels: number;
|
||||
}
|
||||
export const getMetaDataFromSpanTree = (
|
||||
treeData: ITraceTree,
|
||||
): GetTraceMetaData => {
|
||||
let globalStart = Number.POSITIVE_INFINITY;
|
||||
let globalEnd = Number.NEGATIVE_INFINITY;
|
||||
let totalSpans = 0;
|
||||
let levels = 1;
|
||||
const traverse = (treeNode: ITraceTree, level = 0) => {
|
||||
const traverse = (treeNode: ITraceTree, level = 0): void => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
totalSpans++;
|
||||
totalSpans += 1;
|
||||
levels = Math.max(levels, level);
|
||||
const { startTime } = treeNode;
|
||||
const endTime = startTime + treeNode.value;
|
||||
globalStart = Math.min(globalStart, startTime);
|
||||
globalEnd = Math.max(globalEnd, endTime);
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
treeNode.children.forEach((childNode) => {
|
||||
traverse(childNode, level + 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(treeData, 1);
|
||||
|
||||
@ -34,7 +43,9 @@ export const getMetaDataFromSpanTree = (treeData: ITraceTree) => {
|
||||
};
|
||||
};
|
||||
|
||||
export function getTopLeftFromBody(elem: HTMLElement) {
|
||||
export function getTopLeftFromBody(
|
||||
elem: HTMLElement,
|
||||
): { top: number; left: number } {
|
||||
const box = elem.getBoundingClientRect();
|
||||
|
||||
const { body } = document;
|
||||
@ -57,18 +68,18 @@ export const getNodeById = (
|
||||
treeData: ITraceTree,
|
||||
): ITraceTree | undefined => {
|
||||
let foundNode: ITraceTree | undefined;
|
||||
const traverse = (treeNode: ITraceTree, level = 0) => {
|
||||
const traverse = (treeNode: ITraceTree, level = 0): void => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchingId == treeNode.id) {
|
||||
if (searchingId === treeNode.id) {
|
||||
foundNode = treeNode;
|
||||
}
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
treeNode.children.forEach((childNode) => {
|
||||
traverse(childNode, level + 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(treeData, 1);
|
||||
|
||||
@ -88,7 +99,7 @@ const getSpanWithoutChildren = (
|
||||
tags: span.tags,
|
||||
time: span.time,
|
||||
value: span.value,
|
||||
error: span.error,
|
||||
event: span.event,
|
||||
hasError: span.hasError,
|
||||
};
|
||||
};
|
||||
@ -101,10 +112,7 @@ export const isSpanPresentInSearchString = (
|
||||
|
||||
const stringifyTree = JSON.stringify(parsedTree);
|
||||
|
||||
if (stringifyTree.includes(searchedString)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return stringifyTree.includes(searchedString);
|
||||
};
|
||||
|
||||
export const isSpanPresent = (
|
||||
@ -117,7 +125,7 @@ export const isSpanPresent = (
|
||||
treeNode: ITraceTree,
|
||||
level = 0,
|
||||
foundNode: ITraceTree[],
|
||||
) => {
|
||||
): void => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
@ -128,9 +136,9 @@ export const isSpanPresent = (
|
||||
foundNode.push(treeNode);
|
||||
}
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
treeNode.children.forEach((childNode) => {
|
||||
traverse(childNode, level + 1, foundNode);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(tree, 1, foundNode);
|
||||
|
||||
@ -140,7 +148,7 @@ export const isSpanPresent = (
|
||||
export const getSpanPath = (tree: ITraceTree, spanId: string): string[] => {
|
||||
const spanPath: string[] = [];
|
||||
|
||||
const traverse = (treeNode: ITraceTree) => {
|
||||
const traverse = (treeNode: ITraceTree): boolean => {
|
||||
if (!treeNode) {
|
||||
return false;
|
||||
}
|
||||
@ -152,9 +160,9 @@ export const getSpanPath = (tree: ITraceTree, spanId: string): string[] => {
|
||||
}
|
||||
|
||||
let foundInChild = false;
|
||||
for (const childNode of treeNode.children) {
|
||||
treeNode.children.forEach((childNode) => {
|
||||
if (traverse(childNode)) foundInChild = true;
|
||||
}
|
||||
});
|
||||
if (!foundInChild) {
|
||||
spanPath.pop();
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import { Button, Menu } from 'antd';
|
||||
import { MenuInfo } from 'rc-menu/lib/interface';
|
||||
import React from 'react';
|
||||
|
||||
import { SettingPeroid } from '.';
|
||||
@ -35,7 +34,7 @@ function Retention({
|
||||
];
|
||||
|
||||
const onClickHandler = (
|
||||
e: MenuInfo,
|
||||
e: { key: string },
|
||||
func: React.Dispatch<React.SetStateAction<SettingPeroid>>,
|
||||
): void => {
|
||||
const selected = e.key as SettingPeroid;
|
||||
|
@ -39,6 +39,10 @@ function GeneralSettings(): JSX.Element {
|
||||
const [isDefaultMetrics, setIsDefaultMetrics] = useState<boolean>(false);
|
||||
const [isDefaultTrace, setIsDefaultTrace] = useState<boolean>(false);
|
||||
|
||||
const onModalToggleHandler = (): void => {
|
||||
setModal((modal) => !modal);
|
||||
};
|
||||
|
||||
const onClickSaveHandler = useCallback(() => {
|
||||
onModalToggleHandler();
|
||||
}, []);
|
||||
@ -48,10 +52,6 @@ function GeneralSettings(): JSX.Element {
|
||||
undefined
|
||||
>(getRetentionperoidApi, undefined);
|
||||
|
||||
const onModalToggleHandler = (): void => {
|
||||
setModal((modal) => !modal);
|
||||
};
|
||||
|
||||
const checkMetricTraceDefault = (trace: number, metric: number): void => {
|
||||
if (metric === -1) {
|
||||
setIsDefaultMetrics(true);
|
||||
@ -68,12 +68,15 @@ function GeneralSettings(): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && payload !== undefined) {
|
||||
const { metrics_ttl_duration_hrs, traces_ttl_duration_hrs } = payload;
|
||||
const {
|
||||
metrics_ttl_duration_hrs: metricTllDuration,
|
||||
traces_ttl_duration_hrs: traceTllDuration,
|
||||
} = payload;
|
||||
|
||||
checkMetricTraceDefault(traces_ttl_duration_hrs, metrics_ttl_duration_hrs);
|
||||
checkMetricTraceDefault(traceTllDuration, metricTllDuration);
|
||||
|
||||
const traceValue = getSettingsPeroid(traces_ttl_duration_hrs);
|
||||
const metricsValue = getSettingsPeroid(metrics_ttl_duration_hrs);
|
||||
const traceValue = getSettingsPeroid(traceTllDuration);
|
||||
const metricsValue = getSettingsPeroid(metricTllDuration);
|
||||
|
||||
setRetentionPeroidTrace(traceValue.value.toString());
|
||||
setSelectedTracePeroid(traceValue.peroid);
|
||||
@ -171,11 +174,7 @@ function GeneralSettings(): JSX.Element {
|
||||
};
|
||||
|
||||
const isDisabledHandler = (): boolean => {
|
||||
if (retentionPeroidTrace === '' || retentionPeroidMetrics === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return !!(retentionPeroidTrace === '' || retentionPeroidMetrics === '');
|
||||
};
|
||||
|
||||
const errorText = getErrorText();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Typography } from 'antd';
|
||||
import { ChartData } from 'chart.js';
|
||||
import Graph, { graphOnClickHandler } from 'components/Graph';
|
||||
import Graph, { GraphOnClickHandler } from 'components/Graph';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import ValueGraph from 'components/ValueGraph';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
@ -77,9 +77,17 @@ export interface GridGraphComponentProps {
|
||||
title?: string;
|
||||
opacity?: string;
|
||||
isStacked?: boolean;
|
||||
onClickHandler?: graphOnClickHandler;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
name: string;
|
||||
yAxisUnit?: string;
|
||||
}
|
||||
|
||||
GridGraphComponent.defaultProps = {
|
||||
title: undefined,
|
||||
opacity: undefined,
|
||||
isStacked: undefined,
|
||||
onClickHandler: undefined,
|
||||
yAxisUnit: undefined,
|
||||
};
|
||||
|
||||
export default GridGraphComponent;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Graph, { graphOnClickHandler } from 'components/Graph';
|
||||
import Graph, { GraphOnClickHandler } from 'components/Graph';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
@ -60,6 +60,7 @@ function EmptyGraph({
|
||||
|
||||
return (
|
||||
<Graph
|
||||
name=""
|
||||
{...{
|
||||
type: 'line',
|
||||
onClickHandler,
|
||||
@ -84,7 +85,7 @@ function EmptyGraph({
|
||||
interface EmptyGraphProps {
|
||||
selectedTime: timePreferance;
|
||||
widget: Widgets;
|
||||
onClickHandler: graphOnClickHandler | undefined;
|
||||
onClickHandler: GraphOnClickHandler | undefined;
|
||||
}
|
||||
|
||||
export default EmptyGraph;
|
||||
|
@ -2,7 +2,7 @@ import { Button, Typography } from 'antd';
|
||||
import getQueryResult from 'api/widgets/getQuery';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ChartData } from 'chart.js';
|
||||
import { graphOnClickHandler } from 'components/Graph';
|
||||
import { GraphOnClickHandler } from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
import GridGraphComponent from 'container/GridGraphComponent';
|
||||
@ -65,7 +65,9 @@ function FullView({
|
||||
minTime,
|
||||
});
|
||||
|
||||
const getMinMax = (time: timePreferenceType) => {
|
||||
const getMinMax = (
|
||||
time: timePreferenceType,
|
||||
): { min: string | number; max: string | number } => {
|
||||
if (time === 'GLOBAL_TIME') {
|
||||
const minMax = GetMinMax(globalSelectedTime);
|
||||
return {
|
||||
@ -142,7 +144,7 @@ function FullView({
|
||||
loading: false,
|
||||
}));
|
||||
}
|
||||
}, [widget, maxTime, minTime, selectedTime.enum]);
|
||||
}, [widget, maxTime, minTime, selectedTime.enum, globalSelectedTime]);
|
||||
|
||||
useEffect(() => {
|
||||
onFetchDataHandler();
|
||||
@ -240,10 +242,17 @@ interface FullViewState {
|
||||
interface FullViewProps {
|
||||
widget: Widgets;
|
||||
fullViewOptions?: boolean;
|
||||
onClickHandler?: graphOnClickHandler;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
noDataGraph?: boolean;
|
||||
name: string;
|
||||
yAxisUnit?: string;
|
||||
}
|
||||
|
||||
FullView.defaultProps = {
|
||||
fullViewOptions: undefined,
|
||||
onClickHandler: undefined,
|
||||
noDataGraph: undefined,
|
||||
yAxisUnit: undefined,
|
||||
};
|
||||
|
||||
export default FullView;
|
||||
|
@ -124,6 +124,13 @@ function GridCardGraph({
|
||||
[],
|
||||
);
|
||||
|
||||
const onDeleteHandler = useCallback(() => {
|
||||
deleteWidget({ widgetId: widget.id });
|
||||
onToggleModal(setDeletModal);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
isDeleted.current = true;
|
||||
}, [deleteWidget, widget, onToggleModal, isDeleted]);
|
||||
|
||||
const getModals = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
@ -160,12 +167,6 @@ function GridCardGraph({
|
||||
);
|
||||
};
|
||||
|
||||
const onDeleteHandler = useCallback(() => {
|
||||
deleteWidget({ widgetId: widget.id });
|
||||
onToggleModal(setDeletModal);
|
||||
isDeleted.current = true;
|
||||
}, [deleteWidget, widget, onToggleModal, isDeleted]);
|
||||
|
||||
if (state.error) {
|
||||
return (
|
||||
<>
|
||||
|
@ -6,11 +6,9 @@ import { notification } from 'antd';
|
||||
import updateDashboardApi from 'api/dashboard/update';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import history from 'lib/history';
|
||||
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
import { v4 } from 'uuid';
|
||||
@ -156,7 +154,8 @@ function GridGraph(): JSX.Element {
|
||||
});
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: error.toString() || 'Something went wrong',
|
||||
message:
|
||||
error instanceof Error ? error.toString() : 'Something went wrong',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { Breadcrumb } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
const breadcrumbNameMap = {
|
||||
[ROUTES.APPLICATION]: 'Application',
|
||||
[ROUTES.TRACES]: 'Traces',
|
||||
[ROUTES.TRACE]: 'Traces',
|
||||
[ROUTES.SERVICE_MAP]: 'Service Map',
|
||||
[ROUTES.USAGE_EXPLORER]: 'Usage Explorer',
|
||||
[ROUTES.INSTRUMENTATION]: 'Add instrumentation',
|
||||
@ -15,7 +14,9 @@ const breadcrumbNameMap = {
|
||||
};
|
||||
|
||||
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {
|
||||
const pathArray = props.location.pathname.split('/').filter((i) => i);
|
||||
const { location } = props;
|
||||
|
||||
const pathArray = location.pathname.split('/').filter((i) => i);
|
||||
|
||||
const extraBreadcrumbItems = pathArray.map((_, index) => {
|
||||
const url = `/${pathArray.slice(0, index + 1).join('/')}`;
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
import { Modal } from 'antd';
|
||||
import DatePicker from 'components/DatePicker';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
@ -22,10 +23,7 @@ function CustomDateTimeModal({
|
||||
}
|
||||
|
||||
function disabledDate(current: Dayjs): boolean {
|
||||
if (current > dayjs()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return current > dayjs();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,25 +1,25 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
type fiveMin = '5min';
|
||||
type fifteenMin = '15min';
|
||||
type thrityMin = '30min';
|
||||
type oneMin = '1min';
|
||||
type sixHour = '6hr';
|
||||
type oneHour = '1hr';
|
||||
type oneDay = '1day';
|
||||
type oneWeek = '1week';
|
||||
type custom = 'custom';
|
||||
type FiveMin = '5min';
|
||||
type FifteenMin = '15min';
|
||||
type ThirtyMin = '30min';
|
||||
type OneMin = '1min';
|
||||
type SixHour = '6hr';
|
||||
type OneHour = '1hr';
|
||||
type OneDay = '1day';
|
||||
type OneWeek = '1week';
|
||||
type Custom = 'custom';
|
||||
|
||||
export type Time =
|
||||
| fiveMin
|
||||
| fifteenMin
|
||||
| thrityMin
|
||||
| oneMin
|
||||
| sixHour
|
||||
| oneHour
|
||||
| custom
|
||||
| oneWeek
|
||||
| oneDay;
|
||||
| FiveMin
|
||||
| FifteenMin
|
||||
| ThirtyMin
|
||||
| OneMin
|
||||
| SixHour
|
||||
| OneHour
|
||||
| Custom
|
||||
| OneWeek
|
||||
| OneDay;
|
||||
|
||||
export const Options: Option[] = [
|
||||
{ value: '5min', label: 'Last 5 min' },
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Button, Select as DefaultSelect } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import { LOCAL_STORAGE } from 'constants/localStorage';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GlobalTimeLoading, UpdateTimeInterval } from 'store/actions';
|
||||
@ -26,7 +26,7 @@ function DateTimeSelection({
|
||||
updateTimeInterval,
|
||||
globalTimeLoading,
|
||||
}: Props): JSX.Element {
|
||||
const [form_dtselector] = Form.useForm();
|
||||
const [formSelector] = Form.useForm();
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const searchStartTime = params.get('startTime');
|
||||
@ -72,10 +72,27 @@ function DateTimeSelection({
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const getInputLabel = (
|
||||
startTime?: Dayjs,
|
||||
endTime?: Dayjs,
|
||||
timeInterval: Time = '15min',
|
||||
): string | Time => {
|
||||
if (startTime && endTime && timeInterval === 'custom') {
|
||||
const format = 'YYYY/MM/DD HH:mm';
|
||||
|
||||
const startString = startTime.format(format);
|
||||
const endString = endTime.format(format);
|
||||
|
||||
return `${startString} - ${endString}`;
|
||||
}
|
||||
|
||||
return timeInterval;
|
||||
};
|
||||
|
||||
const getDefaultTime = (pathName: string): Time => {
|
||||
const defaultSelectedOption = getDefaultOption(pathName);
|
||||
|
||||
const routes = getLocalStorageKey(LOCAL_STORAGE.METRICS_TIME_IN_DURATION);
|
||||
const routes = getLocalStorageKey(LOCALSTORAGE.METRICS_TIME_IN_DURATION);
|
||||
|
||||
if (routes !== null) {
|
||||
const routesObject = JSON.parse(routes || '{}');
|
||||
@ -94,7 +111,7 @@ function DateTimeSelection({
|
||||
);
|
||||
|
||||
const updateLocalStorageForRoutes = (value: Time): void => {
|
||||
const preRoutes = getLocalStorageKey(LOCAL_STORAGE.METRICS_TIME_IN_DURATION);
|
||||
const preRoutes = getLocalStorageKey(LOCALSTORAGE.METRICS_TIME_IN_DURATION);
|
||||
if (preRoutes !== null) {
|
||||
const preRoutesObject = JSON.parse(preRoutes);
|
||||
|
||||
@ -104,46 +121,12 @@ function DateTimeSelection({
|
||||
preRoute[location.pathname] = value;
|
||||
|
||||
setLocalStorageKey(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
LOCALSTORAGE.METRICS_TIME_IN_DURATION,
|
||||
JSON.stringify(preRoute),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectHandler = (value: Time): void => {
|
||||
if (value !== 'custom') {
|
||||
updateTimeInterval(value);
|
||||
const selectedLabel = getInputLabel(undefined, undefined, value);
|
||||
setSelectedTimeInterval(selectedLabel as Time);
|
||||
updateLocalStorageForRoutes(value);
|
||||
} else {
|
||||
setRefreshButtonHidden(true);
|
||||
setCustomDTPickerVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onRefreshHandler = (): void => {
|
||||
onSelectHandler(selectedTimeInterval);
|
||||
onLastRefreshHandler();
|
||||
};
|
||||
|
||||
const getInputLabel = (
|
||||
startTime?: Dayjs,
|
||||
endTime?: Dayjs,
|
||||
timeInterval: Time = '15min',
|
||||
): string | Time => {
|
||||
if (startTime && endTime && timeInterval === 'custom') {
|
||||
const format = 'YYYY/MM/DD HH:mm';
|
||||
|
||||
const startString = startTime.format(format);
|
||||
const endString = endTime.format(format);
|
||||
|
||||
return `${startString} - ${endString}`;
|
||||
}
|
||||
|
||||
return timeInterval;
|
||||
};
|
||||
|
||||
const onLastRefreshHandler = useCallback(() => {
|
||||
const currentTime = dayjs();
|
||||
|
||||
@ -177,6 +160,23 @@ function DateTimeSelection({
|
||||
return `Last refresh - ${secondsDiff} sec ago`;
|
||||
}, [maxTime, minTime, selectedTimeInterval]);
|
||||
|
||||
const onSelectHandler = (value: Time): void => {
|
||||
if (value !== 'custom') {
|
||||
updateTimeInterval(value);
|
||||
const selectedLabel = getInputLabel(undefined, undefined, value);
|
||||
setSelectedTimeInterval(selectedLabel as Time);
|
||||
updateLocalStorageForRoutes(value);
|
||||
} else {
|
||||
setRefreshButtonHidden(true);
|
||||
setCustomDTPickerVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onRefreshHandler = (): void => {
|
||||
onSelectHandler(selectedTimeInterval);
|
||||
onLastRefreshHandler();
|
||||
};
|
||||
|
||||
const onCustomDateHandler = (dateTimeRange: DateTimeRangeType): void => {
|
||||
if (dateTimeRange !== null) {
|
||||
const [startTimeMoment, endTimeMoment] = dateTimeRange;
|
||||
@ -199,12 +199,12 @@ function DateTimeSelection({
|
||||
// this is triggred when we change the routes and based on that we are changing the default options
|
||||
useEffect(() => {
|
||||
const metricsTimeDuration = getLocalStorageKey(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
LOCALSTORAGE.METRICS_TIME_IN_DURATION,
|
||||
);
|
||||
|
||||
if (metricsTimeDuration === null) {
|
||||
setLocalStorageKey(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
LOCALSTORAGE.METRICS_TIME_IN_DURATION,
|
||||
JSON.stringify({}),
|
||||
);
|
||||
}
|
||||
@ -252,12 +252,12 @@ function DateTimeSelection({
|
||||
return (
|
||||
<Container>
|
||||
<Form
|
||||
form={form_dtselector}
|
||||
form={formSelector}
|
||||
layout="inline"
|
||||
initialValues={{ interval: selectedTime }}
|
||||
>
|
||||
<DefaultSelect
|
||||
onSelect={(value): void => onSelectHandler(value as Time)}
|
||||
onSelect={(value: unknown): void => onSelectHandler(value as Time)}
|
||||
value={getInputLabel(startTime, endTime, selectedTime)}
|
||||
data-testid="dropDown"
|
||||
>
|
||||
|
@ -2,7 +2,7 @@ import { Col } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
|
||||
import ShowBreadcrumbs from './Breadcrumbs';
|
||||
import DateTimeSelector from './DateTimeSelection';
|
||||
@ -21,7 +21,7 @@ function TopNav(): JSX.Element | null {
|
||||
}
|
||||
|
||||
const checkRouteExists = (currentPath: string): boolean => {
|
||||
for (let i = 0; i < routesToSkip.length; ++i) {
|
||||
for (let i = 0; i < routesToSkip.length; i += 1) {
|
||||
if (
|
||||
matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true })
|
||||
) {
|
||||
|
@ -21,6 +21,8 @@ function DeleteAlert({
|
||||
payload: undefined,
|
||||
});
|
||||
|
||||
const defaultErrorMessage = 'Something went wrong';
|
||||
|
||||
const onDeleteHandler = async (id: number): Promise<void> => {
|
||||
try {
|
||||
setDeleteAlertState((state) => ({
|
||||
@ -48,11 +50,11 @@ function DeleteAlert({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: response.error || 'Something went wrong',
|
||||
errorMessage: response.error || defaultErrorMessage,
|
||||
}));
|
||||
|
||||
notifications.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
message: response.error || defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@ -60,11 +62,11 @@ function DeleteAlert({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: 'Something went wrong',
|
||||
errorMessage: defaultErrorMessage,
|
||||
}));
|
||||
|
||||
notifications.error({
|
||||
message: 'Something went wrong',
|
||||
message: defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ import ROUTES from 'constants/routes';
|
||||
import useInterval from 'hooks/useInterval';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { generatePath } from 'react-router';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { Alerts } from 'types/api/alerts/getAll';
|
||||
|
||||
import DeleteAlert from './DeleteAlert';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Button } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
@ -7,6 +6,7 @@ import { DeleteDashboard, DeleteDashboardProps } from 'store/actions';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
import { Data } from '../index';
|
||||
import { TableLinkText } from './styles';
|
||||
|
||||
function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element {
|
||||
const onClickHandler = useCallback(() => {
|
||||
@ -15,11 +15,7 @@ function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element {
|
||||
});
|
||||
}, [id, deleteDashboard]);
|
||||
|
||||
return (
|
||||
<Button onClick={onClickHandler} type="link">
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
return <TableLinkText onClick={onClickHandler}>Delete</TableLinkText>;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
@ -40,10 +36,18 @@ const WrapperDeleteButton = connect(null, mapDispatchToProps)(DeleteButton);
|
||||
|
||||
// This is to avoid the type collision
|
||||
function Wrapper(props: Data): JSX.Element {
|
||||
const { createdBy, description, id, key, lastUpdatedTime, name, tags } = props;
|
||||
|
||||
return (
|
||||
<WrapperDeleteButton
|
||||
{...{
|
||||
...props,
|
||||
createdBy,
|
||||
description,
|
||||
id,
|
||||
key,
|
||||
lastUpdatedTime,
|
||||
name,
|
||||
tags,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -1,25 +1,23 @@
|
||||
import { Button } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
|
||||
import { Data } from '..';
|
||||
import { TableLinkText } from './styles';
|
||||
|
||||
function Name(name: Data['name'], data: Data): JSX.Element {
|
||||
const onClickHandler = () => {
|
||||
const onClickHandler = (): void => {
|
||||
const { id: DashboardId } = data;
|
||||
|
||||
history.push(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: data.id,
|
||||
dashboardId: DashboardId,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={onClickHandler} type="link">
|
||||
{name}
|
||||
</Button>
|
||||
);
|
||||
return <TableLinkText onClick={onClickHandler}>{name}</TableLinkText>;
|
||||
}
|
||||
|
||||
export default Name;
|
||||
|
@ -1,12 +1,13 @@
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import { Tag } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
import { Data } from '../index';
|
||||
|
||||
function Tags(props: Data['tags']): JSX.Element {
|
||||
function Tags(data: Data['tags']): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{props.map((e) => (
|
||||
{data.map((e) => (
|
||||
<Tag key={e}>{e}</Tag>
|
||||
))}
|
||||
</>
|
||||
|
@ -0,0 +1,8 @@
|
||||
import { blue } from '@ant-design/colors';
|
||||
import { Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TableLinkText = styled(Typography.Text)`
|
||||
color: ${blue.primary} !important;
|
||||
cursor: pointer;
|
||||
`;
|
@ -187,7 +187,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
widget={getWidget([
|
||||
{
|
||||
query: `sum(rate(signoz_latency_count{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[2m]))`,
|
||||
legend: 'Request per second',
|
||||
legend: 'Requests',
|
||||
},
|
||||
])}
|
||||
yAxisUnit="reqps"
|
||||
@ -222,7 +222,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
widget={getWidget([
|
||||
{
|
||||
query: `max(sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."}[1m]))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[1m]))) < 1000 OR vector(0)`,
|
||||
legend: 'Error Percentage (%)',
|
||||
legend: 'Error Percentage',
|
||||
},
|
||||
])}
|
||||
yAxisUnit="%"
|
||||
|
@ -9,6 +9,8 @@ import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
||||
function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
|
||||
const legend = '{{http_url}}';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={24}>
|
||||
@ -23,7 +25,7 @@ function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
widget={getWidget([
|
||||
{
|
||||
query: `max((sum(rate(signoz_external_call_latency_count{service_name="${servicename}", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_external_call_latency_count{service_name="${servicename}", http_status_code=~"5.."}[1m]) OR vector(0)) by (http_url))*100/sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[1m])) by (http_url)) < 1000 OR vector(0)`,
|
||||
legend: '{{http_url}}',
|
||||
legend,
|
||||
},
|
||||
])}
|
||||
yAxisUnit="%"
|
||||
@ -65,7 +67,7 @@ function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
widget={getWidget([
|
||||
{
|
||||
query: `sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m])) by (http_url)`,
|
||||
legend: '{{http_url}}',
|
||||
legend,
|
||||
},
|
||||
])}
|
||||
yAxisUnit="reqps"
|
||||
@ -85,7 +87,7 @@ function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
widget={getWidget([
|
||||
{
|
||||
query: `(sum(rate(signoz_external_call_latency_sum{service_name="${servicename}"}[5m])) by (http_url))/(sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m])) by (http_url))`,
|
||||
legend: '{{http_url}}',
|
||||
legend,
|
||||
},
|
||||
])}
|
||||
yAxisUnit="ms"
|
||||
|
@ -2,19 +2,20 @@ import { Button, Table, Tooltip } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { topEndpointListItem } from 'store/actions/MetricsActions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import history from 'lib/history';
|
||||
|
||||
function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const { data } = props;
|
||||
|
||||
const params = useParams<{ servicename: string }>();
|
||||
|
||||
const handleOnClick = (operation: string): void => {
|
||||
@ -80,7 +81,7 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
|
||||
title: 'Number of Calls',
|
||||
dataIndex: 'numCalls',
|
||||
key: 'numCalls',
|
||||
sorter: (a: topEndpointListItem, b: topEndpointListItem): number =>
|
||||
sorter: (a: TopEndpointListItem, b: TopEndpointListItem): number =>
|
||||
a.numCalls - b.numCalls,
|
||||
},
|
||||
];
|
||||
@ -91,7 +92,7 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
|
||||
title={(): string => {
|
||||
return 'Top Endpoints';
|
||||
}}
|
||||
dataSource={props.data}
|
||||
dataSource={data}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
rowKey="name"
|
||||
@ -99,10 +100,18 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
type DataProps = topEndpointListItem;
|
||||
interface TopEndpointListItem {
|
||||
p50: number;
|
||||
p95: number;
|
||||
p99: number;
|
||||
numCalls: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
type DataProps = TopEndpointListItem;
|
||||
|
||||
interface TopEndpointsTableProps {
|
||||
data: topEndpointListItem[];
|
||||
data: TopEndpointListItem[];
|
||||
}
|
||||
|
||||
export default TopEndpointsTable;
|
||||
|
@ -22,6 +22,7 @@ function SkipOnBoardingModal({ onContinueClick }: Props): JSX.Element {
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
title="youtube_video"
|
||||
/>
|
||||
<div>
|
||||
<Typography>No instrumentation data.</Typography>
|
||||
|
@ -6,8 +6,8 @@ import ROUTES from 'constants/routes';
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { servicesListItem } from 'store/actions/MetricsActions/metricsInterfaces';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ServicesList } from 'types/api/metrics/getService';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
import SkipBoardModal from './SkipOnBoardModal';
|
||||
@ -27,10 +27,6 @@ function Metrics(): JSX.Element {
|
||||
setSkipOnboarding(true);
|
||||
};
|
||||
|
||||
const onClickHandler = (to: string): void => {
|
||||
window.open(to, '_blank');
|
||||
};
|
||||
|
||||
if (
|
||||
services.length === 0 &&
|
||||
loading === false &&
|
||||
@ -88,6 +84,6 @@ function Metrics(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
type DataProps = servicesListItem;
|
||||
type DataProps = ServicesList;
|
||||
|
||||
export default Metrics;
|
||||
|
@ -1,6 +1,4 @@
|
||||
import TimeSeries, {
|
||||
TimeSeriesProps as IconProps,
|
||||
} from 'assets/Dashboard/TimeSeries';
|
||||
import TimeSeries from 'assets/Dashboard/TimeSeries';
|
||||
import ValueIcon from 'assets/Dashboard/Value';
|
||||
|
||||
const Items: ItemsProps[] = [
|
||||
@ -24,4 +22,8 @@ interface ItemsProps {
|
||||
display: string;
|
||||
}
|
||||
|
||||
interface IconProps {
|
||||
fillColor: React.CSSProperties['color'];
|
||||
}
|
||||
|
||||
export default Items;
|
||||
|
@ -2,9 +2,9 @@ import { Button, Divider } from 'antd';
|
||||
import Input from 'components/Input';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useLocation } from 'react-router';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { DeleteQuery } from 'store/actions';
|
||||
@ -12,8 +12,11 @@ import {
|
||||
UpdateQuery,
|
||||
UpdateQueryProps,
|
||||
} from 'store/actions/dashboard/updateQuery';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { DeleteQueryProps } from 'types/actions/dashboard';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
|
||||
import {
|
||||
ButtonContainer,
|
||||
@ -32,10 +35,27 @@ function Query({
|
||||
const [promqlQuery, setPromqlQuery] = useState(preQuery);
|
||||
const [legendFormat, setLegendFormat] = useState(preLegend);
|
||||
const { search } = useLocation();
|
||||
const { dashboards } = useSelector<AppState, DashboardReducer>(
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
|
||||
const [selectedDashboards] = dashboards;
|
||||
const { widgets } = selectedDashboards.data;
|
||||
|
||||
const query = new URLSearchParams(search);
|
||||
const widgetId = query.get('widgetId') || '';
|
||||
|
||||
const urlQuery = useMemo(() => {
|
||||
return new URLSearchParams(search);
|
||||
}, [search]);
|
||||
|
||||
const getWidget = useCallback(() => {
|
||||
const widgetId = urlQuery.get('widgetId');
|
||||
return widgets?.find((e) => e.id === widgetId);
|
||||
}, [widgets, urlQuery]);
|
||||
|
||||
const selectedWidget = getWidget() as Widgets;
|
||||
|
||||
const onChangeHandler = useCallback(
|
||||
(setFunc: React.Dispatch<React.SetStateAction<string>>, value: string) => {
|
||||
setFunc(value);
|
||||
@ -49,6 +69,7 @@ function Query({
|
||||
legend: legendFormat,
|
||||
query: promqlQuery,
|
||||
widgetId,
|
||||
yAxisUnit: selectedWidget.yAxisUnit,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { PlusOutlined } from '@ant-design/icons';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { CreateQuery, CreateQueryProps } from 'store/actions';
|
||||
|
@ -4,7 +4,7 @@ import { NewWidgetProps } from 'container/NewWidget';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
|
||||
|
@ -4,14 +4,26 @@ import React from 'react';
|
||||
|
||||
import { flattenedCategories } from './dataFormatCategories';
|
||||
|
||||
const findCategoryById = (searchValue) =>
|
||||
find(flattenedCategories, (option) => option.id == searchValue);
|
||||
const findCategoryByName = (searchValue) =>
|
||||
find(flattenedCategories, (option) => option.name == searchValue);
|
||||
const findCategoryById = (
|
||||
searchValue: string,
|
||||
): Record<string, string> | undefined =>
|
||||
find(flattenedCategories, (option) => option.id === searchValue);
|
||||
const findCategoryByName = (
|
||||
searchValue: string,
|
||||
): Record<string, string> | undefined =>
|
||||
find(flattenedCategories, (option) => option.name === searchValue);
|
||||
|
||||
function YAxisUnitSelector({ defaultValue, onSelect, fieldLabel }): JSX.Element {
|
||||
function YAxisUnitSelector({
|
||||
defaultValue,
|
||||
onSelect,
|
||||
fieldLabel,
|
||||
}: {
|
||||
defaultValue: string;
|
||||
onSelect: React.Dispatch<React.SetStateAction<string>>;
|
||||
fieldLabel: string;
|
||||
}): JSX.Element {
|
||||
const onSelectHandler = (selectedValue: string): void => {
|
||||
onSelect(findCategoryByName(selectedValue)?.id);
|
||||
onSelect(findCategoryByName(selectedValue)?.id || '');
|
||||
};
|
||||
const options = flattenedCategories.map((options) => ({
|
||||
value: options.name,
|
||||
@ -26,9 +38,14 @@ function YAxisUnitSelector({ defaultValue, onSelect, fieldLabel }): JSX.Element
|
||||
options={options}
|
||||
defaultValue={findCategoryById(defaultValue)?.name}
|
||||
onSelect={onSelectHandler}
|
||||
filterOption={(inputValue, option): boolean =>
|
||||
option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
filterOption={(inputValue, option): boolean => {
|
||||
if (option) {
|
||||
return (
|
||||
option.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<Input size="large" placeholder="Unit" allowClear />
|
||||
</AutoComplete>
|
||||
|
@ -1,23 +1,11 @@
|
||||
import {
|
||||
// Button,
|
||||
Input,
|
||||
// Slider,
|
||||
// Switch,
|
||||
// Typography,
|
||||
} from 'antd';
|
||||
import { Input } from 'antd';
|
||||
import InputComponent from 'components/Input';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { dataTypeCategories } from './dataFormatCategories';
|
||||
import {
|
||||
Container,
|
||||
// NullButtonContainer, TextContainer,
|
||||
Title,
|
||||
} from './styles';
|
||||
// import {ca} from '@grafana/data'
|
||||
import { Container, Title } from './styles';
|
||||
import { timePreferance } from './timeItems';
|
||||
import YAxisUnitSelector from './YAxisUnitSelector';
|
||||
|
||||
@ -25,14 +13,8 @@ const { TextArea } = Input;
|
||||
|
||||
function RightContainer({
|
||||
description,
|
||||
// opacity,
|
||||
// selectedNullZeroValue,
|
||||
setDescription,
|
||||
// setOpacity,
|
||||
// setSelectedNullZeroValue,
|
||||
// setStacked,
|
||||
setTitle,
|
||||
// stacked,
|
||||
title,
|
||||
selectedGraph,
|
||||
setSelectedTime,
|
||||
@ -47,21 +29,6 @@ function RightContainer({
|
||||
[],
|
||||
);
|
||||
|
||||
// const nullValueButtons = [
|
||||
// {
|
||||
// check: 'zero',
|
||||
// name: 'Zero',
|
||||
// },
|
||||
// {
|
||||
// check: 'interpolate',
|
||||
// name: 'Interpolate',
|
||||
// },
|
||||
// {
|
||||
// check: 'blank',
|
||||
// name: 'Blank',
|
||||
// },
|
||||
// ];
|
||||
|
||||
const selectedGraphType =
|
||||
GraphTypes.find((e) => e.name === selectedGraph)?.display || '';
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
export const timeItems: timePreferance[] = [
|
||||
{
|
||||
name: 'Global Time',
|
||||
|
@ -5,8 +5,7 @@ import history from 'lib/history';
|
||||
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useLocation, useParams } from 'react-router';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { generatePath, useLocation, useParams } from 'react-router-dom';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { ApplySettingsToPanel, ApplySettingsToPanelProps } from 'store/actions';
|
||||
@ -30,7 +29,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import LeftContainer from './LeftContainer';
|
||||
import RightContainer from './RightContainer';
|
||||
import timeItems, { timePreferance } from './RightContainer/timeItems';
|
||||
import TimeItems, { timePreferance } from './RightContainer/timeItems';
|
||||
import {
|
||||
ButtonContainer,
|
||||
Container,
|
||||
@ -91,7 +90,7 @@ function NewWidget({
|
||||
|
||||
const getSelectedTime = useCallback(
|
||||
() =>
|
||||
timeItems.find(
|
||||
TimeItems.find(
|
||||
(e) => e.enum === (selectedWidget?.timePreferance || 'GLOBAL_TIME'),
|
||||
),
|
||||
[selectedWidget],
|
||||
|
@ -3,7 +3,7 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import setTheme from 'lib/theme/setTheme';
|
||||
import setTheme, { AppMode } from 'lib/theme/setTheme';
|
||||
import React, { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
@ -36,10 +36,10 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
const preMode: appMode = isDarkMode ? 'lightMode' : 'darkMode';
|
||||
const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode';
|
||||
setTheme(preMode);
|
||||
|
||||
const id: appMode = preMode;
|
||||
const id: AppMode = preMode;
|
||||
const { head } = document;
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
@ -115,8 +115,6 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
type appMode = 'darkMode' | 'lightMode';
|
||||
|
||||
interface DispatchProps {
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Layout, Menu, Switch, Typography } from 'antd';
|
||||
import { Layout, Switch, Typography } from 'antd';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const { Sider: SiderComponent } = Layout;
|
||||
@ -37,7 +38,7 @@ interface DarkModeProps {
|
||||
|
||||
export const ToggleButton = styled(Switch)<DarkModeProps>`
|
||||
&&& {
|
||||
background: ${({ checked }) => checked === false && 'grey'};
|
||||
background: ${({ checked }): string => (checked === false ? 'grey' : '')};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -51,11 +52,11 @@ export const SlackMenuItemContainer = styled.div<LogoProps>`
|
||||
position: fixed;
|
||||
bottom: 48px;
|
||||
background: #262626;
|
||||
width: ${({ collapsed }) => (!collapsed ? '200px' : '80px')};
|
||||
width: ${({ collapsed }): string => (!collapsed ? '200px' : '80px')};
|
||||
|
||||
&&& {
|
||||
li {
|
||||
${({ collapsed }) =>
|
||||
${({ collapsed }): StyledCSS =>
|
||||
collapsed &&
|
||||
css`
|
||||
padding-left: 24px;
|
||||
@ -63,9 +64,9 @@ export const SlackMenuItemContainer = styled.div<LogoProps>`
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-left: ${({ collapsed }) => (collapsed ? '0' : '24px')};
|
||||
margin-left: ${({ collapsed }): string => (collapsed ? '0' : '24px')};
|
||||
|
||||
${({ collapsed }) =>
|
||||
${({ collapsed }): StyledCSS =>
|
||||
collapsed &&
|
||||
css`
|
||||
height: 100%;
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { StyledDiv } from 'components/Styled';
|
||||
import { ITraceMetaData } from 'container/GantChart';
|
||||
import { IIntervalUnit, INTERVAL_UNITS } from 'container/TraceDetail/utils';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { styles, Svg, TimelineInterval } from './styles';
|
||||
import { Interval } from './types';
|
||||
import { getIntervals, getIntervalSpread } from './utils';
|
||||
|
||||
const Timeline_Height = 22;
|
||||
const Timeline_H_Spacing = 0;
|
||||
const TimelineHeight = 22;
|
||||
const TimelineHSpacing = 0;
|
||||
|
||||
function Timeline({
|
||||
traceMetaData,
|
||||
@ -19,6 +20,10 @@ function Timeline({
|
||||
const [ref, { width }] = useMeasure<HTMLDivElement>();
|
||||
const { isDarkMode } = useThemeMode();
|
||||
|
||||
const asd = useRef('');
|
||||
|
||||
asd.current = '1';
|
||||
|
||||
const [intervals, setIntervals] = useState<Interval[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -32,10 +37,10 @@ function Timeline({
|
||||
});
|
||||
|
||||
let intervalUnit = INTERVAL_UNITS[0];
|
||||
for (let idx = 0; idx < INTERVAL_UNITS.length; idx++) {
|
||||
const standard_interval = INTERVAL_UNITS[idx];
|
||||
if (baseSpread * standard_interval.multiplier < 1) {
|
||||
const index = parseInt(idx, 10);
|
||||
for (let idx = 0; idx < INTERVAL_UNITS.length; idx += 1) {
|
||||
const standardInterval = INTERVAL_UNITS[idx];
|
||||
if (baseSpread * standardInterval.multiplier < 1) {
|
||||
const index = idx;
|
||||
if (index > 1) intervalUnit = INTERVAL_UNITS[index - 1];
|
||||
break;
|
||||
}
|
||||
@ -54,16 +59,16 @@ function Timeline({
|
||||
}, [traceMetaData, globalTraceMetadata, setIntervalUnit]);
|
||||
|
||||
return (
|
||||
<StyledDiv ref={ref} styledclass={[styles.timelineContainer]}>
|
||||
<StyledDiv ref={ref as never} styledclass={[styles.timelineContainer]}>
|
||||
<Svg
|
||||
viewBox={`0 0 ${width} ${Timeline_Height}`}
|
||||
viewBox={`0 0 ${width} ${TimelineHeight}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1={Timeline_H_Spacing}
|
||||
y1={Timeline_Height}
|
||||
x2={width - Timeline_H_Spacing}
|
||||
y2={Timeline_Height}
|
||||
x1={TimelineHSpacing}
|
||||
y1={TimelineHeight}
|
||||
x2={width - TimelineHSpacing}
|
||||
y2={TimelineHeight}
|
||||
stroke={isDarkMode ? 'white' : 'black'}
|
||||
strokeWidth="1"
|
||||
/>
|
||||
@ -71,8 +76,8 @@ function Timeline({
|
||||
intervals.map((interval, index) => (
|
||||
<TimelineInterval
|
||||
transform={`translate(${
|
||||
Timeline_H_Spacing +
|
||||
(interval.percentage * (width - 2 * Timeline_H_Spacing)) / 100
|
||||
TimelineHSpacing +
|
||||
(interval.percentage * (width - 2 * TimelineHSpacing)) / 100
|
||||
},0)`}
|
||||
key={`${interval.label + interval.percentage + index}`}
|
||||
>
|
||||
@ -80,8 +85,8 @@ function Timeline({
|
||||
{interval.label}
|
||||
</text>
|
||||
<line
|
||||
y1={Timeline_Height - 5}
|
||||
y2={Timeline_Height + 0.5}
|
||||
y1={TimelineHeight - 5}
|
||||
y2={TimelineHeight + 0.5}
|
||||
stroke={isDarkMode ? 'white' : 'black'}
|
||||
strokeWidth="1"
|
||||
/>
|
||||
@ -100,8 +105,7 @@ interface TimelineProps {
|
||||
totalSpans: number;
|
||||
levels: number;
|
||||
};
|
||||
globalTraceMetadata: Record<string, number>;
|
||||
intervalUnit: IIntervalUnit;
|
||||
globalTraceMetadata: ITraceMetaData;
|
||||
setIntervalUnit: React.Dispatch<React.SetStateAction<IIntervalUnit>>;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ITraceMetaData } from 'container/GantChart';
|
||||
import {
|
||||
INTERVAL_UNITS,
|
||||
IIntervalUnit,
|
||||
resolveTimeFromInterval,
|
||||
} from 'container/TraceDetail/utils';
|
||||
import { isEqual } from 'lodash-es';
|
||||
@ -10,6 +11,9 @@ import { Interval } from './types';
|
||||
export const getIntervalSpread = ({
|
||||
localTraceMetaData,
|
||||
globalTraceMetadata,
|
||||
}: {
|
||||
localTraceMetaData: ITraceMetaData;
|
||||
globalTraceMetadata: ITraceMetaData;
|
||||
}): {
|
||||
baseInterval: number;
|
||||
baseSpread: number;
|
||||
@ -46,6 +50,11 @@ export const getIntervals = ({
|
||||
baseSpread,
|
||||
intervalSpreadNormalized,
|
||||
intervalUnit,
|
||||
}: {
|
||||
baseInterval: number;
|
||||
baseSpread: number;
|
||||
intervalSpreadNormalized: number;
|
||||
intervalUnit: IIntervalUnit;
|
||||
}): Interval[] => {
|
||||
const intervals: Interval[] = [
|
||||
{
|
||||
@ -60,21 +69,21 @@ export const getIntervals = ({
|
||||
let elapsedIntervals = 0;
|
||||
|
||||
while (tempBaseSpread && intervals.length < 20) {
|
||||
let interval_time;
|
||||
let intervalTime;
|
||||
if (tempBaseSpread <= 1.5 * intervalSpreadNormalized) {
|
||||
interval_time = elapsedIntervals + tempBaseSpread;
|
||||
intervalTime = elapsedIntervals + tempBaseSpread;
|
||||
tempBaseSpread = 0;
|
||||
} else {
|
||||
interval_time = elapsedIntervals + intervalSpreadNormalized;
|
||||
intervalTime = elapsedIntervals + intervalSpreadNormalized;
|
||||
tempBaseSpread -= intervalSpreadNormalized;
|
||||
}
|
||||
elapsedIntervals = interval_time;
|
||||
elapsedIntervals = intervalTime;
|
||||
const interval: Interval = {
|
||||
label: `${toFixed(
|
||||
resolveTimeFromInterval(interval_time + baseInterval, intervalUnit),
|
||||
resolveTimeFromInterval(intervalTime + baseInterval, intervalUnit),
|
||||
2,
|
||||
)}${intervalUnit.name}`,
|
||||
percentage: (interval_time / baseSpread) * 100,
|
||||
percentage: (intervalTime / baseSpread) * 100,
|
||||
};
|
||||
intervals.push(interval);
|
||||
}
|
||||
|
@ -2,10 +2,8 @@ import { Checkbox, notification, Typography } from 'antd';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { AxiosError } from 'axios';
|
||||
import React, { useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { SelectedTraceFilter } from 'store/actions/trace/selectTraceFilter';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { getFilter, updateURL } from 'store/actions/trace/util';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
@ -30,17 +28,18 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { keyValue, name, value } = props;
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const isUserSelected =
|
||||
(userSelectedFilter.get(props.name) || []).find(
|
||||
(e) => e === props.keyValue,
|
||||
) !== undefined;
|
||||
(userSelectedFilter.get(name) || []).find((e) => e === keyValue) !==
|
||||
undefined;
|
||||
|
||||
const onCheckHandler = async () => {
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const onCheckHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
@ -48,48 +47,46 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
const preUserSelectedMap = new Map(userSelectedFilter);
|
||||
const preIsFilterExclude = new Map(isFilterExclude);
|
||||
|
||||
const isTopicPresent = preUserSelectedMap.get(props.name);
|
||||
const isTopicPresent = preUserSelectedMap.get(name);
|
||||
|
||||
// append the value
|
||||
if (!isTopicPresent) {
|
||||
preUserSelectedMap.set(props.name, [props.keyValue]);
|
||||
preUserSelectedMap.set(name, [keyValue]);
|
||||
} else {
|
||||
const isValuePresent =
|
||||
isTopicPresent.find((e) => e === props.keyValue) !== undefined;
|
||||
isTopicPresent.find((e) => e === keyValue) !== undefined;
|
||||
|
||||
// check the value if present then remove the value or isChecked
|
||||
if (isValuePresent) {
|
||||
preUserSelectedMap.set(
|
||||
props.name,
|
||||
isTopicPresent.filter((e) => e !== props.keyValue),
|
||||
name,
|
||||
isTopicPresent.filter((e) => e !== keyValue),
|
||||
);
|
||||
} else {
|
||||
// if not present add into the array of string
|
||||
preUserSelectedMap.set(props.name, [...isTopicPresent, props.keyValue]);
|
||||
preUserSelectedMap.set(name, [...isTopicPresent, keyValue]);
|
||||
}
|
||||
}
|
||||
|
||||
if (newSelectedMap.get(props.name)?.find((e) => e === props.keyValue)) {
|
||||
newSelectedMap.set(props.name, [
|
||||
...(newSelectedMap.get(props.name) || []).filter(
|
||||
(e) => e !== props.keyValue,
|
||||
),
|
||||
if (newSelectedMap.get(name)?.find((e) => e === keyValue)) {
|
||||
newSelectedMap.set(name, [
|
||||
...(newSelectedMap.get(name) || []).filter((e) => e !== keyValue),
|
||||
]);
|
||||
} else {
|
||||
newSelectedMap.set(props.name, [
|
||||
...new Set([...(newSelectedMap.get(props.name) || []), props.keyValue]),
|
||||
newSelectedMap.set(name, [
|
||||
...new Set([...(newSelectedMap.get(name) || []), keyValue]),
|
||||
]);
|
||||
}
|
||||
|
||||
if (preIsFilterExclude.get(props.name) !== false) {
|
||||
preIsFilterExclude.set(props.name, true);
|
||||
if (preIsFilterExclude.get(name) !== false) {
|
||||
preIsFilterExclude.set(name, true);
|
||||
}
|
||||
|
||||
const response = await getFilters({
|
||||
other: Object.fromEntries(newSelectedMap),
|
||||
end: String(globalTime.maxTime),
|
||||
start: String(globalTime.minTime),
|
||||
getFilters: filterToFetchData.filter((e) => e !== props.name),
|
||||
getFilters: filterToFetchData.filter((e) => e !== name),
|
||||
isFilterExclude: preIsFilterExclude,
|
||||
});
|
||||
|
||||
@ -97,15 +94,15 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
const updatedFilter = getFilter(response.payload);
|
||||
|
||||
updatedFilter.forEach((value, key) => {
|
||||
if (key !== 'duration' && props.name !== key) {
|
||||
if (key !== 'duration' && name !== key) {
|
||||
preUserSelectedMap.set(key, Object.keys(value));
|
||||
}
|
||||
});
|
||||
|
||||
updatedFilter.set(props.name, {
|
||||
[`${props.keyValue}`]: '-1',
|
||||
...(filter.get(props.name) || {}),
|
||||
...(updatedFilter.get(props.name) || {}),
|
||||
updatedFilter.set(name, {
|
||||
[`${keyValue}`]: '-1',
|
||||
...(filter.get(name) || {}),
|
||||
...(updatedFilter.get(name) || {}),
|
||||
});
|
||||
|
||||
dispatch({
|
||||
@ -156,12 +153,12 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
onClick={onCheckHandler}
|
||||
checked={isCheckBoxSelected}
|
||||
defaultChecked
|
||||
key={props.keyValue}
|
||||
key={keyValue}
|
||||
>
|
||||
{props.keyValue}
|
||||
{keyValue}
|
||||
</Checkbox>
|
||||
{isCheckBoxSelected ? (
|
||||
<Typography>{props.value}</Typography>
|
||||
<Typography>{value}</Typography>
|
||||
) : (
|
||||
<Typography>-</Typography>
|
||||
)}
|
||||
@ -169,23 +166,10 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
selectedTraceFilter: (props: {
|
||||
topic: TraceFilterEnum;
|
||||
value: string;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
interface CheckBoxProps extends DispatchProps {
|
||||
interface CheckBoxProps {
|
||||
keyValue: string;
|
||||
value: string;
|
||||
name: TraceFilterEnum;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
selectedTraceFilter: bindActionCreators(SelectedTraceFilter, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(CheckBoxComponent);
|
||||
export default CheckBoxComponent;
|
||||
|
@ -10,7 +10,9 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
|
||||
(state) => state.traces,
|
||||
);
|
||||
|
||||
const status = filter.get(props.name) || {};
|
||||
const { name } = props;
|
||||
|
||||
const status = filter.get(name) || {};
|
||||
|
||||
const statusObj = Object.keys(status);
|
||||
|
||||
@ -20,7 +22,7 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
|
||||
<CheckBoxComponent
|
||||
key={e}
|
||||
{...{
|
||||
name: props.name,
|
||||
name,
|
||||
keyValue: e,
|
||||
value: status[e],
|
||||
}}
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import { Input, Slider } from 'antd';
|
||||
import { SliderRangeProps } from 'antd/lib/slider';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
@ -18,7 +19,7 @@ import { Container, InputContainer, Text } from './styles';
|
||||
|
||||
dayjs.extend(durationPlugin);
|
||||
|
||||
const getMs = (value: string) => {
|
||||
const getMs = (value: string): string => {
|
||||
return dayjs
|
||||
.duration({
|
||||
milliseconds: parseInt(value, 10) / 1000000,
|
||||
@ -42,7 +43,9 @@ function Duration(): JSX.Element {
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const getDuration = () => {
|
||||
const getDuration = ():
|
||||
| { maxDuration: string; minDuration: string }
|
||||
| Record<string, string> => {
|
||||
const selectedDuration = selectedFilter.get('duration');
|
||||
|
||||
if (selectedDuration) {
|
||||
@ -65,7 +68,7 @@ function Duration(): JSX.Element {
|
||||
|
||||
const defaultValue = [parseFloat(minDuration), parseFloat(maxDuration)];
|
||||
|
||||
const updatedUrl = async (min: number, max: number) => {
|
||||
const updatedUrl = async (min: number, max: number): Promise<void> => {
|
||||
const preSelectedFilter = new Map(selectedFilter);
|
||||
const preUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
@ -114,7 +117,7 @@ function Duration(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const onRangeSliderHandler = (number: [number, number]) => {
|
||||
const onRangeSliderHandler = (number: [number, number]): void => {
|
||||
const [min, max] = number;
|
||||
|
||||
setLocalMin(min.toString());
|
||||
@ -124,11 +127,10 @@ function Duration(): JSX.Element {
|
||||
const debouncedFunction = useDebouncedFn(
|
||||
(min, max) => {
|
||||
console.log('debounce function');
|
||||
updatedUrl(min, max);
|
||||
updatedUrl(min as number, max as number);
|
||||
},
|
||||
500,
|
||||
undefined,
|
||||
[],
|
||||
);
|
||||
|
||||
const onChangeMaxHandler: React.ChangeEventHandler<HTMLInputElement> = (
|
||||
@ -187,21 +189,16 @@ function Duration(): JSX.Element {
|
||||
min={parseFloat((filter.get('duration') || {}).minDuration)}
|
||||
max={parseFloat((filter.get('duration') || {}).maxDuration)}
|
||||
range
|
||||
tipFormatter={(value) => {
|
||||
tipFormatter={(value): JSX.Element => {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
return <div />;
|
||||
}
|
||||
return <div>{`${getMs(value.toString())}ms`}</div>;
|
||||
}}
|
||||
onChange={([min, max]) => {
|
||||
onChange={([min, max]): void => {
|
||||
onRangeSliderHandler([min, max]);
|
||||
}}
|
||||
onAfterChange={onRangeHandler}
|
||||
// onAfterChange={([min, max]) => {
|
||||
// const returnFunction = debounce((min, max) => updatedUrl(min, max));
|
||||
|
||||
// returnFunction(min, max);
|
||||
// }}
|
||||
value={[parseFloat(localMin), parseFloat(localMax)]}
|
||||
/>
|
||||
</Container>
|
||||
|
@ -38,8 +38,10 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
userSelectedFilter,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const { name: PanelName, isOpen: IsPanelOpen } = props;
|
||||
|
||||
const isDefaultOpen =
|
||||
filterToFetchData.find((e) => e === props.name) !== undefined;
|
||||
filterToFetchData.find((e) => e === PanelName) !== undefined;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
@ -49,6 +51,9 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const defaultErrorMessage = 'Something went wrong';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const onExpandHandler: React.MouseEventHandler<HTMLDivElement> = async (e) => {
|
||||
try {
|
||||
e.preventDefault();
|
||||
@ -60,14 +65,14 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
const getPreUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
if (!isDefaultOpen) {
|
||||
updatedFilterData = [props.name];
|
||||
updatedFilterData = [PanelName];
|
||||
} else {
|
||||
// removing the selected filter
|
||||
updatedFilterData = [
|
||||
...filterToFetchData.filter((name) => name !== props.name),
|
||||
...filterToFetchData.filter((name) => name !== PanelName),
|
||||
];
|
||||
getprepdatedSelectedFilter.delete(props.name);
|
||||
getPreUserSelected.delete(props.name);
|
||||
getprepdatedSelectedFilter.delete(PanelName);
|
||||
getPreUserSelected.delete(PanelName);
|
||||
}
|
||||
|
||||
const response = await getFilters({
|
||||
@ -89,11 +94,11 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
// );
|
||||
|
||||
getPreUserSelected.set(
|
||||
props.name,
|
||||
Object.keys(updatedFilter.get(props.name) || {}),
|
||||
PanelName,
|
||||
Object.keys(updatedFilter.get(PanelName) || {}),
|
||||
);
|
||||
|
||||
updatedFilterData = [...filterToFetchData, props.name];
|
||||
updatedFilterData = [...filterToFetchData, PanelName];
|
||||
}
|
||||
|
||||
// now append the non prop.name trace filter enum over the list
|
||||
@ -104,12 +109,12 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
// });
|
||||
|
||||
getPreUserSelected.forEach((value, key) => {
|
||||
if (key !== props.name) {
|
||||
if (key !== PanelName) {
|
||||
getPreUserSelected.set(key, value);
|
||||
}
|
||||
});
|
||||
filter.forEach((value, key) => {
|
||||
if (key !== props.name) {
|
||||
if (key !== PanelName) {
|
||||
updatedFilter.set(key, value);
|
||||
}
|
||||
});
|
||||
@ -138,30 +143,30 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
);
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
message: response.error || defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: (error as AxiosError).toString() || 'Something went wrong',
|
||||
message: (error as AxiosError).toString() || defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onClearAllHandler = async () => {
|
||||
const onClearAllHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const updatedFilter = new Map(selectedFilter);
|
||||
const preUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
updatedFilter.delete(props.name);
|
||||
preUserSelected.delete(props.name);
|
||||
updatedFilter.delete(PanelName);
|
||||
preUserSelected.delete(PanelName);
|
||||
|
||||
const postIsFilterExclude = new Map(isFilterExclude);
|
||||
|
||||
postIsFilterExclude.set(props.name, false);
|
||||
postIsFilterExclude.set(PanelName, false);
|
||||
|
||||
const response = await getFilters({
|
||||
end: String(global.maxTime),
|
||||
@ -267,25 +272,25 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.name !== 'duration' && <Divider plain style={{ margin: 0 }} />}
|
||||
{PanelName !== 'duration' && <Divider plain style={{ margin: 0 }} />}
|
||||
|
||||
<Card bordered={false}>
|
||||
<Container
|
||||
disabled={filterLoading || isLoading}
|
||||
aria-disabled={filterLoading || isLoading}
|
||||
aria-expanded={props.isOpen}
|
||||
aria-expanded={IsPanelOpen}
|
||||
>
|
||||
<TextCotainer onClick={onExpandHandler}>
|
||||
<IconContainer>
|
||||
{!props.isOpen ? <RightOutlined /> : <DownOutlined />}
|
||||
{!IsPanelOpen ? <RightOutlined /> : <DownOutlined />}
|
||||
</IconContainer>
|
||||
|
||||
<Text style={{ textTransform: 'capitalize' }} ellipsis>
|
||||
{AllPanelHeading.find((e) => e.key === props.name)?.displayValue || ''}
|
||||
{AllPanelHeading.find((e) => e.key === PanelName)?.displayValue || ''}
|
||||
</Text>
|
||||
</TextCotainer>
|
||||
|
||||
{props.name !== 'duration' && (
|
||||
{PanelName !== 'duration' && (
|
||||
<ButtonContainer>
|
||||
{/* <ButtonComponent
|
||||
aria-disabled={isLoading || filterLoading}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Button } from 'antd';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
@ -13,9 +14,9 @@ export const Container = styled.div<Props>`
|
||||
padding-left: 0.5rem;
|
||||
min-height: 5vh;
|
||||
|
||||
cursor: ${({ disabled }) => disabled && 'not-allowed'};
|
||||
cursor: ${({ disabled }): string => (disabled ? 'not-allowed' : '')};
|
||||
|
||||
${({ disabled }) =>
|
||||
${({ disabled }): StyledCSS =>
|
||||
disabled &&
|
||||
css`
|
||||
opacity: 0.5;
|
||||
|
@ -9,14 +9,16 @@ import PanelHeading from './PanelHeading';
|
||||
function Panel(props: PanelProps): JSX.Element {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const { name } = props;
|
||||
|
||||
const isDefaultOpen =
|
||||
traces.filterToFetchData.find((e) => e === props.name) !== undefined;
|
||||
traces.filterToFetchData.find((e) => e === name) !== undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelHeading name={props.name} isOpen={isDefaultOpen} />
|
||||
<PanelHeading name={name} isOpen={isDefaultOpen} />
|
||||
|
||||
{isDefaultOpen && <PanelBody type={props.name} />}
|
||||
{isDefaultOpen && <PanelBody type={name} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Input } from 'antd';
|
||||
import { Input } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DurationContainer = styled.div`
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { ChartData, ChartDataset, ChartDatasetProperties } from 'chart.js';
|
||||
import { ChartData, ChartDatasetProperties } from 'chart.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import { keys } from 'lodash-es';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
function transposeArray(array: number[][], arrayLength: number) {
|
||||
function transposeArray(array: number[][], arrayLength: number): number[][] {
|
||||
const newArray: number[][] = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
for (let i = 0; i < array.length; i += 1) {
|
||||
newArray.push([]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
for (let j = 0; j < arrayLength; j++) {
|
||||
for (let i = 0; i < array.length; i += 1) {
|
||||
for (let j = 0; j < arrayLength; j += 1) {
|
||||
newArray[j]?.push(array[i][j]);
|
||||
}
|
||||
}
|
||||
@ -69,6 +69,8 @@ export const getChartDataforGroupBy = (
|
||||
const allGroupBy = Object.keys(items).map((e) => items[e].groupBy);
|
||||
|
||||
keys(allGroupBy).forEach((e: string): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const { length } = keys(allGroupBy[e]);
|
||||
|
||||
if (length >= max) {
|
||||
|
@ -43,7 +43,7 @@ function TraceGraph(): JSX.Element {
|
||||
}
|
||||
|
||||
return (
|
||||
<Container ref={ref}>
|
||||
<Container ref={ref as never}>
|
||||
<Graph
|
||||
animate={false}
|
||||
data={ChartData}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
@ -13,7 +14,7 @@ export const Container = styled.div<Props>`
|
||||
overflow: auto;
|
||||
width: 100% !important;
|
||||
|
||||
${({ center }) =>
|
||||
${({ center }): StyledCSS =>
|
||||
center &&
|
||||
css`
|
||||
display: flex;
|
||||
|
@ -12,7 +12,9 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [selectedKey, setSelectedKey] = useState<string>(props.tag.Key[0] || '');
|
||||
const { index, setLocalSelectedTags, tag } = props;
|
||||
|
||||
const [selectedKey, setSelectedKey] = useState<string>(tag.Key[0] || '');
|
||||
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
@ -77,14 +79,14 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
|
||||
if (options && options.find((option) => option.value === value)) {
|
||||
setSelectedKey(value);
|
||||
|
||||
props.setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, props.index),
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: [value],
|
||||
Operator: props.tag.Operator,
|
||||
Values: props.tag.Values,
|
||||
Operator: tag.Operator,
|
||||
Values: tag.Values,
|
||||
},
|
||||
...tags.slice(props.index + 1, tags.length),
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
} else {
|
||||
setSelectedKey('');
|
||||
|
@ -1,13 +1,8 @@
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { Select } from 'antd';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
import React from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { UpdateSelectedTags } from 'store/actions/trace/updateTagsSelected';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import {
|
||||
@ -22,7 +17,12 @@ const { Option } = Select;
|
||||
|
||||
type Tags = FlatArray<TraceReducer['selectedTags'], 1>['Operator'];
|
||||
|
||||
const AllMenu: AllMenu[] = [
|
||||
interface AllMenuProps {
|
||||
key: Tags | '';
|
||||
value: string;
|
||||
}
|
||||
|
||||
const AllMenu: AllMenuProps[] = [
|
||||
{
|
||||
key: 'in',
|
||||
value: 'IN',
|
||||
@ -33,41 +33,40 @@ const AllMenu: AllMenu[] = [
|
||||
},
|
||||
];
|
||||
|
||||
interface AllMenu {
|
||||
key: Tags | '';
|
||||
value: string;
|
||||
}
|
||||
|
||||
function SingleTags(props: AllTagsProps): JSX.Element {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const { tag, onCloseHandler, setLocalSelectedTags, index } = props;
|
||||
const {
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: selectedValues,
|
||||
} = props.tag;
|
||||
} = tag;
|
||||
|
||||
const onDeleteTagHandler = (index: number): void => {
|
||||
props.onCloseHandler(index);
|
||||
onCloseHandler(index);
|
||||
};
|
||||
|
||||
const onChangeOperatorHandler = (key: SelectValue): void => {
|
||||
props.setLocalSelectedTags([
|
||||
...traces.selectedTags.slice(0, props.index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Values: selectedValues,
|
||||
Operator: key as Tags,
|
||||
},
|
||||
...traces.selectedTags.slice(props.index + 1, traces.selectedTags.length),
|
||||
]);
|
||||
const onChangeOperatorHandler = (key: unknown): void => {
|
||||
if (typeof key === 'string') {
|
||||
setLocalSelectedTags([
|
||||
...traces.selectedTags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Values: selectedValues,
|
||||
Operator: key as Tags,
|
||||
},
|
||||
...traces.selectedTags.slice(index + 1, traces.selectedTags.length),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TagsKey
|
||||
index={props.index}
|
||||
tag={props.tag}
|
||||
setLocalSelectedTags={props.setLocalSelectedTags}
|
||||
index={index}
|
||||
tag={tag}
|
||||
setLocalSelectedTags={setLocalSelectedTags}
|
||||
/>
|
||||
|
||||
<SelectComponent
|
||||
@ -84,40 +83,27 @@ function SingleTags(props: AllTagsProps): JSX.Element {
|
||||
<ValueSelect
|
||||
value={selectedValues}
|
||||
onChange={(value): void => {
|
||||
props.setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, props.index),
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: value as string[],
|
||||
},
|
||||
...tags.slice(props.index + 1, tags.length),
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
}}
|
||||
mode="tags"
|
||||
/>
|
||||
|
||||
<IconContainer
|
||||
role="button"
|
||||
onClick={(): void => onDeleteTagHandler(props.index)}
|
||||
>
|
||||
<IconContainer role="button" onClick={(): void => onDeleteTagHandler(index)}>
|
||||
<CloseOutlined />
|
||||
</IconContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateSelectedTags: (props: TraceReducer['selectedTags']) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateSelectedTags: bindActionCreators(UpdateSelectedTags, dispatch),
|
||||
});
|
||||
|
||||
interface AllTagsProps extends DispatchProps {
|
||||
interface AllTagsProps {
|
||||
onCloseHandler: (index: number) => void;
|
||||
index: number;
|
||||
tag: FlatArray<TraceReducer['selectedTags'], 1>;
|
||||
@ -126,4 +112,4 @@ interface AllTagsProps extends DispatchProps {
|
||||
>;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(SingleTags);
|
||||
export default SingleTags;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { CaretRightFilled, PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
@ -48,12 +47,6 @@ function AllTags({
|
||||
]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEqual(traces.selectedTags, localSelectedTags)) {
|
||||
setLocalSelectedTags(traces.selectedTags);
|
||||
}
|
||||
}, [traces.selectedTags, localSelectedTags]);
|
||||
|
||||
const onCloseHandler = (index: number): void => {
|
||||
setLocalSelectedTags([
|
||||
...localSelectedTags.slice(0, index),
|
||||
|
@ -20,7 +20,9 @@ export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
const splitBy = isNotInPresent ? 'not in' : isInPresent ? 'in' : '';
|
||||
const isPresentSplit = isInPresent ? 'in' : '';
|
||||
|
||||
const splitBy = isNotInPresent ? 'not in' : isPresentSplit;
|
||||
|
||||
if (splitBy.length === 0) {
|
||||
isError = true;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { SelectProps, Space } from 'antd';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
import { Space } from 'antd';
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
@ -23,42 +22,43 @@ function TraceGraphFilter(): JSX.Element {
|
||||
>((state) => state.traces);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const onClickSelectedFunctionHandler: SelectProps<SelectValue>['onChange'] = (
|
||||
ev,
|
||||
) => {
|
||||
const selected = functions.find((e) => e.key === ev);
|
||||
if (selected) {
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_FUNCTION,
|
||||
payload: {
|
||||
selectedFunction: selected.key,
|
||||
yAxisUnit: selected.yAxisUnit,
|
||||
},
|
||||
});
|
||||
const onClickSelectedFunctionHandler = (ev: unknown): void => {
|
||||
if (typeof ev === 'string') {
|
||||
const selected = functions.find((e) => e.key === ev);
|
||||
if (selected) {
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_FUNCTION,
|
||||
payload: {
|
||||
selectedFunction: selected.key,
|
||||
yAxisUnit: selected.yAxisUnit,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onClickSelectedGroupByHandler: SelectProps<SelectValue>['onChange'] = (
|
||||
ev,
|
||||
) => {
|
||||
const selected = groupBy.find((e) => e.key === ev);
|
||||
if (selected) {
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_GROUP_BY,
|
||||
payload: {
|
||||
selectedGroupBy: selected.key,
|
||||
},
|
||||
});
|
||||
const onClickSelectedGroupByHandler = (ev: unknown): void => {
|
||||
if (typeof ev === 'string') {
|
||||
const selected = groupBy.find((e) => e.key === ev);
|
||||
if (selected) {
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_GROUP_BY,
|
||||
payload: {
|
||||
selectedGroupBy: selected.key,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<label>Function</label>
|
||||
<label htmlFor="selectedFunction">Function</label>
|
||||
|
||||
<SelectComponent
|
||||
dropdownMatchSelectWidth
|
||||
data-testid="selectedFunction"
|
||||
id="selectedFunction"
|
||||
value={functions.find((e) => selectedFunction === e.key)?.displayValue}
|
||||
onChange={onClickSelectedFunctionHandler}
|
||||
>
|
||||
@ -69,9 +69,10 @@ function TraceGraphFilter(): JSX.Element {
|
||||
))}
|
||||
</SelectComponent>
|
||||
|
||||
<label>Group By</label>
|
||||
<label htmlFor="selectedGroupBy">Group By</label>
|
||||
<SelectComponent
|
||||
dropdownMatchSelectWidth
|
||||
id="selectedGroupBy"
|
||||
data-testid="selectedGroupBy"
|
||||
value={groupBy.find((e) => selectedGroupBy === e.key)?.displayValue}
|
||||
onChange={onClickSelectedGroupByHandler}
|
||||
|
@ -39,6 +39,32 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
return `${ROUTES.TRACE}/${record.traceID}?spanId=${record.spanID}`;
|
||||
};
|
||||
|
||||
const getValue = (value: string, record: TableType): JSX.Element => {
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>{value}</Typography>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const getHttpMethodOrStatus = (
|
||||
value: TableType['httpMethod'],
|
||||
record: TableType,
|
||||
): JSX.Element => {
|
||||
if (value.length === 0) {
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>-</Typography>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Tag color="magenta">{value}</Tag>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<TableType> = [
|
||||
{
|
||||
title: 'Date',
|
||||
@ -58,25 +84,13 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
title: 'Service',
|
||||
dataIndex: 'serviceName',
|
||||
key: 'serviceName',
|
||||
render: (value, record): JSX.Element => {
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>{value}</Typography>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
render: getValue,
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operation',
|
||||
key: 'operation',
|
||||
render: (value, record): JSX.Element => {
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>{value}</Typography>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
render: getValue,
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
@ -96,39 +110,13 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
title: 'Method',
|
||||
dataIndex: 'httpMethod',
|
||||
key: 'httpMethod',
|
||||
render: (value: TableType['httpMethod'], record): JSX.Element => {
|
||||
if (value.length === 0) {
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>-</Typography>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Tag color="magenta">{value}</Tag>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
render: getHttpMethodOrStatus,
|
||||
},
|
||||
{
|
||||
title: 'Status Code',
|
||||
dataIndex: 'httpCode',
|
||||
key: 'httpCode',
|
||||
render: (value: TableType['httpMethod'], record): JSX.Element => {
|
||||
if (value.length === 0) {
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>-</Typography>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Tag color="magenta">{value}</Tag>
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
render: getHttpMethodOrStatus,
|
||||
},
|
||||
];
|
||||
|
||||
@ -137,18 +125,19 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
_,
|
||||
sort,
|
||||
) => {
|
||||
const { order = 'ascend' } = sort;
|
||||
|
||||
if (props.current && props.pageSize) {
|
||||
getSpansAggregate({
|
||||
maxTime: globalTime.maxTime,
|
||||
minTime: globalTime.minTime,
|
||||
selectedFilter,
|
||||
current: props.current,
|
||||
pageSize: props.pageSize,
|
||||
selectedTags,
|
||||
order: order === 'ascend' ? 'ascending' : 'descending',
|
||||
});
|
||||
if (!Array.isArray(sort)) {
|
||||
const { order = 'ascend' } = sort;
|
||||
if (props.current && props.pageSize) {
|
||||
getSpansAggregate({
|
||||
maxTime: globalTime.maxTime,
|
||||
minTime: globalTime.minTime,
|
||||
selectedFilter,
|
||||
current: props.current,
|
||||
pageSize: props.pageSize,
|
||||
selectedTags,
|
||||
order: order === 'ascend' ? 'ascending' : 'descending',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -32,7 +32,6 @@ function ErrorTag({ event }: ErrorTagProps): JSX.Element {
|
||||
key={`${name}${JSON.stringify(attributeMap)}`}
|
||||
defaultActiveKey={[name || attributeMap.event]}
|
||||
expandIconPosition="right"
|
||||
key={name}
|
||||
>
|
||||
<Panel
|
||||
header={name || attributeMap?.event}
|
||||
|
@ -20,7 +20,7 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
|
||||
const { tree } = props;
|
||||
const { isDarkMode } = useThemeMode();
|
||||
if (!tree) {
|
||||
return <></>;
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const { name, tags, serviceName } = tree;
|
||||
@ -79,4 +79,8 @@ interface SelectedSpanDetailsProps {
|
||||
tree?: ITraceTree;
|
||||
}
|
||||
|
||||
SelectedSpanDetails.defaultProps = {
|
||||
tree: undefined,
|
||||
};
|
||||
|
||||
export default SelectedSpanDetails;
|
||||
|
@ -29,7 +29,7 @@ interface CustomSubTextProps {
|
||||
|
||||
export const CustomSubText = styled(Paragraph)<CustomSubTextProps>`
|
||||
&&& {
|
||||
background: ${({ isDarkMode }) => (isDarkMode ? '#444' : '#ddd')};
|
||||
background: ${({ isDarkMode }): string => (isDarkMode ? '#444' : '#ddd')};
|
||||
font-size: 12px;
|
||||
padding: 6px 8px;
|
||||
word-break: break-all;
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
StyledTypography,
|
||||
} from 'components/Styled';
|
||||
import * as StyledStyles from 'components/Styled/styles';
|
||||
import GanttChart from 'container/GantChart';
|
||||
import GanttChart, { ITraceMetaData } from 'container/GantChart';
|
||||
import { getNodeById } from 'container/GantChart/utils';
|
||||
import Timeline from 'container/Timeline';
|
||||
import TraceFlameGraph from 'container/TraceFlameGraph';
|
||||
@ -49,10 +49,13 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
||||
|
||||
const { treeData: tree, ...traceMetaData } = useMemo(() => {
|
||||
const tree = getSortedData(treeData);
|
||||
return getSpanTreeMetadata(tree, spanServiceColors);
|
||||
// Note: Handle undefined
|
||||
/*eslint-disable */
|
||||
return getSpanTreeMetadata(tree as ITraceTree, spanServiceColors);
|
||||
/* eslint-enable */
|
||||
}, [treeData, spanServiceColors]);
|
||||
|
||||
const [globalTraceMetadata] = useState<Record<string, number>>({
|
||||
const [globalTraceMetadata] = useState<ITraceMetaData>({
|
||||
...traceMetaData,
|
||||
});
|
||||
|
||||
@ -73,7 +76,7 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
||||
// setSearchSpanString(value);
|
||||
// setTreeData(spanToTreeUtil(response[0].events));
|
||||
// };
|
||||
const onFocusSelectedSpanHandler = () => {
|
||||
const onFocusSelectedSpanHandler = (): void => {
|
||||
const treeNode = getNodeById(activeSelectedId, tree);
|
||||
if (treeNode) {
|
||||
setTreeData(treeNode);
|
||||
@ -126,7 +129,6 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element {
|
||||
<Timeline
|
||||
globalTraceMetadata={globalTraceMetadata}
|
||||
traceMetaData={traceMetaData}
|
||||
intervalUnit={intervalUnit}
|
||||
setIntervalUnit={setIntervalUnit}
|
||||
/>
|
||||
</StyledCol>
|
||||
|
@ -45,11 +45,13 @@ export const getSortedData = (treeData: ITraceTree): undefined | ITraceTree => {
|
||||
return;
|
||||
}
|
||||
|
||||
// need this rule to disable
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
treeNode.children = sortBy(treeNode.children, (e) => e.startTime);
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
treeNode.children.forEach((childNode) => {
|
||||
traverse(childNode, level + 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(treeData, 1);
|
||||
|
||||
|
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