mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-01 10:32:02 +08:00
commit
aa5100261d
33
.editorconfig
Normal file
33
.editorconfig
Normal file
@ -0,0 +1,33 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
[*.{js,py}]
|
||||
charset = utf-8
|
||||
|
||||
# 4 space indentation
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
# Indentation override for all JS under lib directory
|
||||
[lib/**.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Matches the exact files either package.json or .travis.yml
|
||||
[{package.json,.travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
6
Makefile
6
Makefile
@ -115,11 +115,9 @@ down-arm:
|
||||
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.arm.yaml down -v
|
||||
|
||||
clear-standalone-data:
|
||||
@cd $(STANDALONE_DIRECTORY)
|
||||
@docker run --rm -v "data:/pwd" busybox \
|
||||
@docker run --rm -v "$(PWD)/$(STANDALONE_DIRECTORY)/data:/pwd" busybox \
|
||||
sh -c "cd /pwd && rm -rf alertmanager/* clickhouse/* signoz/*"
|
||||
|
||||
clear-swarm-data:
|
||||
@cd $(SWARM_DIRECTORY)
|
||||
@docker run --rm -v "data:/pwd" busybox \
|
||||
@docker run --rm -v "$(PWD)/$(SWARM_DIRECTORY)/data:/pwd" busybox \
|
||||
sh -c "cd /pwd && rm -rf alertmanager/* clickhouse/* signoz/*"
|
||||
|
@ -1,11 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<logger>
|
||||
<level>trace</level>
|
||||
<log>/var/log/clickhouse-server/clickhouse-server.log</log>
|
||||
<errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
|
||||
<size>1000M</size>
|
||||
<count>10</count>
|
||||
<level>information</level>
|
||||
<console>1</console>
|
||||
</logger>
|
||||
|
||||
<http_port>8123</http_port>
|
||||
@ -45,6 +42,34 @@
|
||||
</client>
|
||||
</openSSL>
|
||||
|
||||
<!-- Example config for tiered storage -->
|
||||
<!-- <storage_configuration>
|
||||
<disks>
|
||||
<default>
|
||||
<keep_free_space_bytes>10485760</keep_free_space_bytes>
|
||||
</default>
|
||||
<s3>
|
||||
<type>s3</type>
|
||||
<endpoint>https://BUCKET-NAME.s3.amazonaws.com/data/</endpoint>
|
||||
<access_key_id>ACCESS-KEY-ID</access_key_id>
|
||||
<secret_access_key>SECRET-ACCESS-KEY</secret_access_key>
|
||||
</s3>
|
||||
</disks>
|
||||
<policies>
|
||||
<tiered>
|
||||
<volumes>
|
||||
<default>
|
||||
<disk>default</disk>
|
||||
</default>
|
||||
<s3>
|
||||
<disk>s3</disk>
|
||||
</s3>
|
||||
</volumes>
|
||||
</tiered>
|
||||
</policies>
|
||||
</storage_configuration> -->
|
||||
|
||||
|
||||
<!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
|
||||
<!--
|
||||
<http_server_default_response><![CDATA[<html ng-app="SMI2"><head><base href="http://ui.tabix.io/"></head><body><div ui-view="" class="content-ui"></div><script src="http://loader.tabix.io/master.js"></script></body></html>]]></http_server_default_response>
|
||||
|
@ -3,12 +3,19 @@ version: "3.9"
|
||||
services:
|
||||
clickhouse:
|
||||
image: yandex/clickhouse-server:21.12.3.32
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# - "8123:8123"
|
||||
volumes:
|
||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||
@ -17,7 +24,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.6.0
|
||||
image: signoz/alertmanager:0.6.1
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
@ -30,7 +37,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.7.4
|
||||
image: signoz/query-service:0.7.5
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
ports:
|
||||
- "8080:8080"
|
||||
@ -45,6 +52,11 @@ services:
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-swarm
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@ -52,8 +64,12 @@ services:
|
||||
- clickhouse
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.7.4
|
||||
image: signoz/frontend:0.7.5
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
ports:
|
||||
- "3301:3301"
|
||||
|
@ -1,11 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<logger>
|
||||
<level>trace</level>
|
||||
<log>/var/log/clickhouse-server/clickhouse-server.log</log>
|
||||
<errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
|
||||
<size>1000M</size>
|
||||
<count>10</count>
|
||||
<level>information</level>
|
||||
<console>1</console>
|
||||
</logger>
|
||||
|
||||
<http_port>8123</http_port>
|
||||
@ -46,30 +43,31 @@
|
||||
</openSSL>
|
||||
|
||||
<!-- Example config for tiered storage -->
|
||||
<!-- <storage_configuration> -->
|
||||
<!-- <disks> -->
|
||||
<!-- <default> -->
|
||||
<!-- </default> -->
|
||||
<!-- <s3> -->
|
||||
<!-- <type>s3</type> -->
|
||||
<!-- <endpoint>http://172.17.0.1:9100/test/random/</endpoint> -->
|
||||
<!-- <access_key_id>ash</access_key_id> -->
|
||||
<!-- <secret_access_key>password</secret_access_key> -->
|
||||
<!-- </s3> -->
|
||||
<!-- </disks> -->
|
||||
<!-- <policies> -->
|
||||
<!-- <tiered> -->
|
||||
<!-- <volumes> -->
|
||||
<!-- <default> -->
|
||||
<!-- <disk>default</disk> -->
|
||||
<!-- </default> -->
|
||||
<!-- <s3> -->
|
||||
<!-- <disk>s3</disk> -->
|
||||
<!-- </s3> -->
|
||||
<!-- </volumes> -->
|
||||
<!-- </tiered> -->
|
||||
<!-- </policies> -->
|
||||
<!-- </storage_configuration> -->
|
||||
<!-- <storage_configuration>
|
||||
<disks>
|
||||
<default>
|
||||
<keep_free_space_bytes>10485760</keep_free_space_bytes>
|
||||
</default>
|
||||
<s3>
|
||||
<type>s3</type>
|
||||
<endpoint>https://BUCKET-NAME.s3.amazonaws.com/data/</endpoint>
|
||||
<access_key_id>ACCESS-KEY-ID</access_key_id>
|
||||
<secret_access_key>SECRET-ACCESS-KEY</secret_access_key>
|
||||
</s3>
|
||||
</disks>
|
||||
<policies>
|
||||
<tiered>
|
||||
<volumes>
|
||||
<default>
|
||||
<disk>default</disk>
|
||||
</default>
|
||||
<s3>
|
||||
<disk>s3</disk>
|
||||
</s3>
|
||||
</volumes>
|
||||
</tiered>
|
||||
</policies>
|
||||
</storage_configuration> -->
|
||||
|
||||
|
||||
<!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
|
||||
|
@ -3,10 +3,17 @@ version: "2.4"
|
||||
services:
|
||||
clickhouse:
|
||||
image: altinity/clickhouse-server:21.12.3.32.altinitydev.arm
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# - "8123:8123"
|
||||
volumes:
|
||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
||||
restart: on-failure
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||
@ -15,17 +22,19 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.6.0
|
||||
image: signoz/alertmanager:0.6.1
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
- query-service
|
||||
query-service:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
command:
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.7.4
|
||||
image: signoz/query-service:0.7.5
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@ -40,14 +49,21 @@ services:
|
||||
- DEPLOYMENT_TYPE=docker-standalone-arm
|
||||
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.7.4
|
||||
image: signoz/frontend:0.7.5
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
ports:
|
||||
- "3301:3301"
|
||||
|
@ -3,10 +3,17 @@ version: "2.4"
|
||||
services:
|
||||
clickhouse:
|
||||
image: yandex/clickhouse-server:21.12.3.32
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# - "8123:8123"
|
||||
volumes:
|
||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
||||
restart: on-failure
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||
@ -15,11 +22,13 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.6.0
|
||||
image: signoz/alertmanager:0.6.1
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
- query-service
|
||||
query-service:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
command:
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
@ -27,7 +36,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.7.4
|
||||
image: signoz/query-service:0.7.5
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@ -41,14 +50,21 @@ services:
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-standalone-amd
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.7.4
|
||||
image: signoz/frontend:0.7.5
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
ports:
|
||||
- "3301:3301"
|
||||
|
@ -247,7 +247,7 @@ bye() { # Prints a friendly good bye message and exits the script.
|
||||
echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack"
|
||||
echo "++++++++++++++++++++++++++++++++++++++++"
|
||||
|
||||
if [[ email == "" ]]; then
|
||||
if [[ $email == "" ]]; then
|
||||
echo -e "\n📨 Please share your email to receive support with the installation"
|
||||
read -rp 'Email: ' email
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
node_modules
|
||||
build
|
||||
build
|
||||
|
6
frontend/babel.config.js
Normal file
6
frontend/babel.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
@ -9,12 +9,19 @@ const config: Config.InitialOptions = {
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
},
|
||||
notify: true,
|
||||
notifyMode: 'always',
|
||||
testMatch: ['<rootDir>/src/**/?(*.)(test).(ts|js)?(x)'],
|
||||
transform: {
|
||||
'\\.(js|jsx|ts|tsx)?$': 'babel-jest',
|
||||
globals: {
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
'ts-jest': {
|
||||
useESM: true,
|
||||
},
|
||||
},
|
||||
testMatch: ['<rootDir>/src/**/?(*.)(test).(ts|js)?(x)'],
|
||||
preset: 'ts-jest/presets/js-with-ts-esm',
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!(lodash-es)/)'],
|
||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
|
@ -57,7 +57,7 @@
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
"jest": "26.6.0",
|
||||
"jest": "^27.5.1",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
@ -68,6 +68,7 @@
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-grid-layout": "^1.2.5",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-query": "^3.34.19",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use": "^17.3.2",
|
||||
@ -107,6 +108,7 @@
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@testing-library/cypress": "^8.0.0",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/compression-webpack-plugin": "^9.0.0",
|
||||
"@types/copy-webpack-plugin": "^8.0.1",
|
||||
@ -155,6 +157,7 @@
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"prettier": "2.2.1",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
|
@ -1,3 +1,27 @@
|
||||
{
|
||||
"monitor_signup": "Monitor your applications. Find what is causing issues."
|
||||
"monitor_signup": "Monitor your applications. Find what is causing issues.",
|
||||
"version": "Version",
|
||||
"latest_version": "Latest version",
|
||||
"current_version": "Current version",
|
||||
"release_notes": "Release Notes",
|
||||
"read_how_to_upgrade": "Read instructions on how to upgrade",
|
||||
"latest_version_signoz": "You are running the latest version of SigNoz.",
|
||||
"stale_version": "You are on an older version and may be loosing on the latest features we have shipped. We recommend to upgrade to the latest version",
|
||||
"oops_something_went_wrong_version": "Oops.. facing issues with fetching updated version information",
|
||||
"n_a": "N/A",
|
||||
"routes": {
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels"
|
||||
},
|
||||
"settings": {
|
||||
"total_retention_period": "Total Retention Period",
|
||||
"move_to_s3": "Move to S3\n(should be lower than total retention period)",
|
||||
"retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.",
|
||||
"retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io",
|
||||
"retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io",
|
||||
"retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.",
|
||||
"retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below",
|
||||
"retention_confirmation": "Are you sure you want to change the retention period?",
|
||||
"retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces."
|
||||
}
|
||||
}
|
||||
|
@ -85,3 +85,7 @@ export const EditAlertChannelsAlerts = Loadable(
|
||||
export const AllAlertChannels = Loadable(
|
||||
() => import(/* webpackChunkName: "All Channels" */ 'pages/AllAlertChannels'),
|
||||
);
|
||||
|
||||
export const StatusPage = Loadable(
|
||||
() => import(/* webpackChunkName: "All Status" */ 'pages/Status'),
|
||||
);
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
ServicesTablePage,
|
||||
SettingsPage,
|
||||
SignupPage,
|
||||
StatusPage,
|
||||
TraceDetail,
|
||||
TraceFilter,
|
||||
UsageExplorerPage,
|
||||
@ -113,6 +114,11 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
component: AllAlertChannels,
|
||||
},
|
||||
{
|
||||
path: ROUTES.VERSION,
|
||||
exact: true,
|
||||
component: StatusPage,
|
||||
},
|
||||
];
|
||||
|
||||
interface AppRoutes {
|
||||
|
24
frontend/src/api/disks/getDisks.ts
Normal file
24
frontend/src/api/disks/getDisks.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/disks/getDisks';
|
||||
|
||||
const getDisks = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/disks`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getDisks;
|
@ -9,7 +9,11 @@ const setRetention = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(
|
||||
`/settings/ttl?duration=${props.duration}&type=${props.type}`,
|
||||
`/settings/ttl?duration=${props.totalDuration}&type=${props.type}${
|
||||
props.coldStorage
|
||||
? `&coldStorage=${props.coldStorage};toColdDuration=${props.toColdDuration}`
|
||||
: ''
|
||||
}`,
|
||||
);
|
||||
|
||||
return {
|
||||
|
28
frontend/src/api/trace/getTagValue.ts
Normal file
28
frontend/src/api/trace/getTagValue.ts
Normal file
@ -0,0 +1,28 @@
|
||||
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/trace/getTagValue';
|
||||
|
||||
const getTagValue = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(`/getTagValues`, {
|
||||
start: props.start.toString(),
|
||||
end: props.end.toString(),
|
||||
tagKey: props.tagKey,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTagValue;
|
25
frontend/src/api/user/getLatestVersion.ts
Normal file
25
frontend/src/api/user/getLatestVersion.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getLatestVersion';
|
||||
|
||||
const getLatestVersion = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://api.github.com/repos/signoz/signoz/releases/latest`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getLatestVersion;
|
17
frontend/src/components/Graph/Plugin/EmptyGraph.ts
Normal file
17
frontend/src/components/Graph/Plugin/EmptyGraph.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { grey } from '@ant-design/colors';
|
||||
import { Chart } from 'chart.js';
|
||||
|
||||
export const emptyGraph = {
|
||||
id: 'emptyChart',
|
||||
afterDraw(chart: Chart): void {
|
||||
const { height, width, ctx } = chart;
|
||||
chart.clear();
|
||||
ctx.save();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = '1.5rem sans-serif';
|
||||
ctx.fillStyle = `${grey.primary}`;
|
||||
ctx.fillText('No data to display', width / 2, height / 2);
|
||||
ctx.restore();
|
||||
},
|
||||
};
|
19
frontend/src/components/Graph/hasData.ts
Normal file
19
frontend/src/components/Graph/hasData.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { ChartData } from 'chart.js';
|
||||
|
||||
export const hasData = (data: ChartData): boolean => {
|
||||
const { datasets = [] } = data;
|
||||
let hasData = false;
|
||||
try {
|
||||
for (const dataset of datasets) {
|
||||
if (dataset.data.length > 0 && dataset.data.some((item) => item !== 0)) {
|
||||
hasData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return hasData;
|
||||
};
|
@ -27,7 +27,9 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { hasData } from './hasData';
|
||||
import { legend } from './Plugin';
|
||||
import { emptyGraph } from './Plugin/EmptyGraph';
|
||||
import { LegendsContainer } from './styles';
|
||||
import { useXAxisTimeUnit } from './xAxisConfig';
|
||||
import { getYAxisFormattedValue } from './yAxisConfig';
|
||||
@ -128,6 +130,7 @@ function Graph({
|
||||
grid: {
|
||||
display: true,
|
||||
color: getGridColor(),
|
||||
drawTicks: true,
|
||||
},
|
||||
adapters: {
|
||||
date: chartjsAdapter,
|
||||
@ -180,12 +183,18 @@ function Graph({
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const chartHasData = hasData(data);
|
||||
const chartPlugins = [];
|
||||
if (chartHasData) {
|
||||
chartPlugins.push(legend(name, data.datasets.length > 3));
|
||||
} else {
|
||||
chartPlugins.push(emptyGraph);
|
||||
}
|
||||
lineChartRef.current = new Chart(chartRef.current, {
|
||||
type,
|
||||
data,
|
||||
options,
|
||||
plugins: [legend(name, data.datasets.length > 3)],
|
||||
plugins: chartPlugins,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { expect } from '@jest/globals';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="sc-gtsrHT VomVY"
|
||||
class="sc-gsDKAQ cLXpIa"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@ -272,21 +272,21 @@ exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
class="sc-hKFxyN dunFuJ"
|
||||
class="sc-hKwDye foaleg"
|
||||
>
|
||||
<p
|
||||
class="sc-dlnjwi cydxLA"
|
||||
class="sc-dkPtRN fcyVIq"
|
||||
>
|
||||
Ah, seems like we reached a dead end!
|
||||
</p>
|
||||
<p
|
||||
class="sc-dlnjwi cydxLA"
|
||||
class="sc-dkPtRN fcyVIq"
|
||||
>
|
||||
Page Not Found
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
class="sc-bdnxRM bYqcho"
|
||||
class="sc-bdvvtL dbTZkj"
|
||||
href="/application"
|
||||
tabindex="0"
|
||||
>
|
||||
|
@ -17,6 +17,7 @@ const ROUTES = {
|
||||
ALL_CHANNELS: '/settings/channels',
|
||||
CHANNELS_NEW: '/setting/channels/new',
|
||||
CHANNELS_EDIT: '/setting/channels/edit/:id',
|
||||
VERSION: '/status',
|
||||
};
|
||||
|
||||
export default ROUTES;
|
||||
|
@ -1,11 +1,24 @@
|
||||
import { notification } from 'antd';
|
||||
import getLatestVersion from 'api/user/getLatestVersion';
|
||||
import getVersion from 'api/user/getVersion';
|
||||
import ROUTES from 'constants/routes';
|
||||
import TopNav from 'container/Header';
|
||||
import SideNav from 'container/SideNav';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import history from 'lib/history';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
UPDATE_CURRENT_ERROR,
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_LATEST_VERSION,
|
||||
UPDATE_LATEST_VERSION_ERROR,
|
||||
} from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { Content, Layout } from './styles';
|
||||
@ -13,11 +26,24 @@ import { Content, Layout } from './styles';
|
||||
function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname);
|
||||
|
||||
const { payload: versionPayload, loading, error: getVersionError } = useFetch(
|
||||
getVersion,
|
||||
);
|
||||
|
||||
const {
|
||||
payload: latestVersionPayload,
|
||||
loading: latestLoading,
|
||||
error: latestError,
|
||||
} = useFetch(getLatestVersion);
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn) {
|
||||
setIsSignUpPage(true);
|
||||
@ -27,6 +53,72 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
}
|
||||
}, [isLoggedIn, isSignUpPage]);
|
||||
|
||||
const latestCurrentCounter = useRef(0);
|
||||
const latestVersionCounter = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn && pathname === ROUTES.SIGN_UP) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
|
||||
if (!latestLoading && latestError && latestCurrentCounter.current === 0) {
|
||||
latestCurrentCounter.current = 1;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_LATEST_VERSION_ERROR,
|
||||
payload: {
|
||||
isError: true,
|
||||
},
|
||||
});
|
||||
notification.error({
|
||||
message: t('oops_something_went_wrong_version'),
|
||||
});
|
||||
}
|
||||
|
||||
if (!loading && getVersionError && latestVersionCounter.current === 0) {
|
||||
latestVersionCounter.current = 1;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_CURRENT_ERROR,
|
||||
payload: {
|
||||
isError: true,
|
||||
},
|
||||
});
|
||||
notification.error({
|
||||
message: t('oops_something_went_wrong_version'),
|
||||
});
|
||||
}
|
||||
|
||||
if (!latestLoading && versionPayload) {
|
||||
dispatch({
|
||||
type: UPDATE_CURRENT_VERSION,
|
||||
payload: {
|
||||
currentVersion: versionPayload.version,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!loading && latestVersionPayload) {
|
||||
dispatch({
|
||||
type: UPDATE_LATEST_VERSION,
|
||||
payload: {
|
||||
latestVersion: latestVersionPayload.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
loading,
|
||||
latestLoading,
|
||||
versionPayload,
|
||||
latestVersionPayload,
|
||||
isLoggedIn,
|
||||
pathname,
|
||||
getVersionError,
|
||||
latestError,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
{!isSignUpPage && <SideNav />}
|
||||
|
@ -18,12 +18,14 @@ function CreateAlertChannels({
|
||||
preType = 'slack',
|
||||
}: CreateAlertChannelsProps): JSX.Element {
|
||||
const [formInstance] = Form.useForm();
|
||||
|
||||
const [selectedConfig, setSelectedConfig] = useState<
|
||||
Partial<SlackChannel & WebhookChannel>
|
||||
>({
|
||||
text: ` {{ range .Alerts -}}
|
||||
*Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||
text: `{{ range .Alerts -}}
|
||||
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||
|
||||
*Summary:* {{ .Annotations.summary }}
|
||||
*Description:* {{ .Annotations.description }}
|
||||
|
||||
*Details:*
|
||||
|
@ -1,108 +1,118 @@
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import { Button, Menu } from 'antd';
|
||||
import React from 'react';
|
||||
import { Col, Row, Select } from 'antd';
|
||||
import { find } from 'lodash-es';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { SettingPeroid } from '.';
|
||||
import {
|
||||
Dropdown,
|
||||
Input,
|
||||
RetentionContainer,
|
||||
TextContainer,
|
||||
Typography,
|
||||
RetentionFieldInputContainer,
|
||||
RetentionFieldLabel,
|
||||
} from './styles';
|
||||
import {
|
||||
convertHoursValueToRelevantUnit,
|
||||
SettingPeriod,
|
||||
TimeUnits,
|
||||
} from './utils';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
function Retention({
|
||||
retentionValue,
|
||||
setRentionValue,
|
||||
selectedRetentionPeroid,
|
||||
setSelectedRetentionPeroid,
|
||||
setRetentionValue,
|
||||
text,
|
||||
}: RetentionProps): JSX.Element {
|
||||
const options: Option[] = [
|
||||
{
|
||||
key: 'hr',
|
||||
value: 'Hrs',
|
||||
},
|
||||
{
|
||||
key: 'day',
|
||||
value: 'Days',
|
||||
},
|
||||
{
|
||||
key: 'month',
|
||||
value: 'Months',
|
||||
},
|
||||
];
|
||||
|
||||
const onClickHandler = (
|
||||
e: { key: string },
|
||||
func: React.Dispatch<React.SetStateAction<SettingPeroid>>,
|
||||
): void => {
|
||||
const selected = e.key as SettingPeroid;
|
||||
func(selected);
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<Menu onClick={(e): void => onClickHandler(e, setSelectedRetentionPeroid)}>
|
||||
{options.map((option) => (
|
||||
<Menu.Item key={option.key}>{option.value}</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
hide,
|
||||
}: RetentionProps): JSX.Element | null {
|
||||
const {
|
||||
value: initialValue,
|
||||
timeUnitValue: initialTimeUnitValue,
|
||||
} = convertHoursValueToRelevantUnit(Number(retentionValue));
|
||||
const [selectedTimeUnit, setSelectTimeUnit] = useState(initialTimeUnitValue);
|
||||
const [selectedValue, setSelectedValue] = useState<number | null>(
|
||||
initialValue,
|
||||
);
|
||||
const interacted = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!interacted.current) setSelectedValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const currentSelectedOption = (option: SettingPeroid): string | undefined => {
|
||||
return options.find((e) => e.key === option)?.value;
|
||||
useEffect(() => {
|
||||
if (!interacted.current) setSelectTimeUnit(initialTimeUnitValue);
|
||||
}, [initialTimeUnitValue]);
|
||||
|
||||
const menuItems = TimeUnits.map((option) => (
|
||||
<Option key={option.value} value={option.value}>
|
||||
{option.key}
|
||||
</Option>
|
||||
));
|
||||
|
||||
const currentSelectedOption = (option: SettingPeriod): void => {
|
||||
const selectedValue = find(TimeUnits, (e) => e.value === option)?.value;
|
||||
if (selectedValue) setSelectTimeUnit(selectedValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const inverseMultiplier = find(
|
||||
TimeUnits,
|
||||
(timeUnit) => timeUnit.value === selectedTimeUnit,
|
||||
)?.multiplier;
|
||||
if (!selectedValue) setRetentionValue(null);
|
||||
if (selectedValue && inverseMultiplier) {
|
||||
setRetentionValue(selectedValue * (1 / inverseMultiplier));
|
||||
}
|
||||
}, [selectedTimeUnit, selectedValue, setRetentionValue]);
|
||||
|
||||
const onChangeHandler = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
func: React.Dispatch<React.SetStateAction<string>>,
|
||||
func: React.Dispatch<React.SetStateAction<number | null>>,
|
||||
): void => {
|
||||
interacted.current = true;
|
||||
const { value } = e.target;
|
||||
const integerValue = parseInt(value, 10);
|
||||
|
||||
if (value.length > 0 && integerValue.toString() === value) {
|
||||
const parsedValue = Math.abs(integerValue).toString();
|
||||
const parsedValue = Math.abs(integerValue);
|
||||
func(parsedValue);
|
||||
}
|
||||
|
||||
if (value.length === 0) {
|
||||
func('');
|
||||
func(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<RetentionContainer>
|
||||
<TextContainer>
|
||||
<Typography>{text}</Typography>
|
||||
</TextContainer>
|
||||
|
||||
<Input
|
||||
value={retentionValue}
|
||||
onChange={(e): void => onChangeHandler(e, setRentionValue)}
|
||||
/>
|
||||
|
||||
<Dropdown overlay={menu}>
|
||||
<Button>
|
||||
{currentSelectedOption(selectedRetentionPeroid)} <DownOutlined />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Row justify="space-between">
|
||||
<Col flex={1} style={{ display: 'flex' }}>
|
||||
<RetentionFieldLabel>{text}</RetentionFieldLabel>
|
||||
</Col>
|
||||
<Col flex="150px">
|
||||
<RetentionFieldInputContainer>
|
||||
<Input
|
||||
value={selectedValue && selectedValue >= 0 ? selectedValue : ''}
|
||||
onChange={(e): void => onChangeHandler(e, setSelectedValue)}
|
||||
style={{ width: 75 }}
|
||||
/>
|
||||
<Select
|
||||
value={selectedTimeUnit}
|
||||
onChange={currentSelectedOption}
|
||||
style={{ width: 100 }}
|
||||
>
|
||||
{menuItems}
|
||||
</Select>
|
||||
</RetentionFieldInputContainer>
|
||||
</Col>
|
||||
</Row>
|
||||
</RetentionContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface Option {
|
||||
key: SettingPeroid;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface RetentionProps {
|
||||
retentionValue: string;
|
||||
retentionValue: number | null;
|
||||
text: string;
|
||||
setRentionValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
selectedRetentionPeroid: SettingPeroid;
|
||||
setSelectedRetentionPeroid: React.Dispatch<
|
||||
React.SetStateAction<SettingPeroid>
|
||||
>;
|
||||
setRetentionValue: React.Dispatch<React.SetStateAction<number | null>>;
|
||||
hide: boolean;
|
||||
}
|
||||
|
||||
export default Retention;
|
||||
|
@ -1,43 +1,73 @@
|
||||
import { Button, Modal, notification, Typography } from 'antd';
|
||||
import getRetentionperoidApi from 'api/settings/getRetention';
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { Button, Col, Modal, notification, Row, Typography } from 'antd';
|
||||
import getDisks from 'api/disks/getDisks';
|
||||
import getRetentionPeriodApi from 'api/settings/getRetention';
|
||||
import setRetentionApi from 'api/settings/setRetention';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import convertIntoHr from 'lib/convertIntoHr';
|
||||
import getSettingsPeroid from 'lib/getSettingsPeroid';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { find } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IDiskType } from 'types/api/disks/getDisks';
|
||||
import { PayloadProps } from 'types/api/settings/getRetention';
|
||||
|
||||
import Retention from './Retention';
|
||||
import {
|
||||
ButtonContainer,
|
||||
Container,
|
||||
ErrorText,
|
||||
ErrorTextContainer,
|
||||
ToolTipContainer,
|
||||
} from './styles';
|
||||
import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles';
|
||||
|
||||
function GeneralSettings(): JSX.Element {
|
||||
const [
|
||||
selectedMetricsPeroid,
|
||||
setSelectedMetricsPeroid,
|
||||
] = useState<SettingPeroid>('month');
|
||||
const { t } = useTranslation();
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
|
||||
const [retentionPeroidMetrics, setRetentionPeroidMetrics] = useState<string>(
|
||||
'',
|
||||
);
|
||||
const [modal, setModal] = useState<boolean>(false);
|
||||
const [postApiLoading, setPostApiLoading] = useState<boolean>(false);
|
||||
|
||||
const [selectedTracePeroid, setSelectedTracePeroid] = useState<SettingPeroid>(
|
||||
'hr',
|
||||
);
|
||||
const [availableDisks, setAvailableDisks] = useState<IDiskType[] | null>(null);
|
||||
|
||||
const [retentionPeroidTrace, setRetentionPeroidTrace] = useState<string>('');
|
||||
const [isDefaultMetrics, setIsDefaultMetrics] = useState<boolean>(false);
|
||||
const [isDefaultTrace, setIsDefaultTrace] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
getDisks().then((response) => setAvailableDisks(response.payload));
|
||||
}, []);
|
||||
|
||||
const { payload, loading, error, errorMessage } = useFetch<
|
||||
PayloadProps,
|
||||
undefined
|
||||
>(getRetentionPeriodApi, undefined);
|
||||
|
||||
const [currentTTLValues, setCurrentTTLValues] = useState(payload);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentTTLValues(payload);
|
||||
}, [payload]);
|
||||
|
||||
const [metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [metricsS3RetentionPeriod, setMetricsS3RetentionPeriod] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [tracesTotalRetentionPeriod, setTracesTotalRetentionPeriod] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [tracesS3RetentionPeriod, setTracesS3RetentionPeriod] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentTTLValues) {
|
||||
setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs);
|
||||
setMetricsS3RetentionPeriod(
|
||||
currentTTLValues.metrics_move_ttl_duration_hrs
|
||||
? currentTTLValues.metrics_move_ttl_duration_hrs
|
||||
: null,
|
||||
);
|
||||
setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs);
|
||||
setTracesS3RetentionPeriod(
|
||||
currentTTLValues.traces_move_ttl_duration_hrs
|
||||
? currentTTLValues.traces_move_ttl_duration_hrs
|
||||
: null,
|
||||
);
|
||||
}
|
||||
console.log({ changed: currentTTLValues });
|
||||
}, [currentTTLValues]);
|
||||
|
||||
const onModalToggleHandler = (): void => {
|
||||
setModal((modal) => !modal);
|
||||
@ -47,182 +77,236 @@ function GeneralSettings(): JSX.Element {
|
||||
onModalToggleHandler();
|
||||
}, []);
|
||||
|
||||
const { payload, loading, error, errorMessage } = useFetch<
|
||||
PayloadProps,
|
||||
undefined
|
||||
>(getRetentionperoidApi, undefined);
|
||||
const s3Enabled = useMemo(
|
||||
() => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'),
|
||||
[availableDisks],
|
||||
);
|
||||
|
||||
const checkMetricTraceDefault = (trace: number, metric: number): void => {
|
||||
if (metric === -1) {
|
||||
setIsDefaultMetrics(true);
|
||||
} else {
|
||||
setIsDefaultMetrics(false);
|
||||
const renderConfig = [
|
||||
{
|
||||
name: 'Metrics',
|
||||
retentionFields: [
|
||||
{
|
||||
name: t('settings.total_retention_period'),
|
||||
value: metricsTotalRetentionPeriod,
|
||||
setValue: setMetricsTotalRetentionPeriod,
|
||||
},
|
||||
{
|
||||
name: t('settings.move_to_s3'),
|
||||
value: metricsS3RetentionPeriod,
|
||||
setValue: setMetricsS3RetentionPeriod,
|
||||
hide: !s3Enabled,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Traces',
|
||||
retentionFields: [
|
||||
{
|
||||
name: t('settings.total_retention_period'),
|
||||
value: tracesTotalRetentionPeriod,
|
||||
setValue: setTracesTotalRetentionPeriod,
|
||||
},
|
||||
{
|
||||
name: t('settings.move_to_s3'),
|
||||
value: tracesS3RetentionPeriod,
|
||||
setValue: setTracesS3RetentionPeriod,
|
||||
hide: !s3Enabled,
|
||||
},
|
||||
],
|
||||
},
|
||||
].map((category): JSX.Element | null => {
|
||||
if (
|
||||
Array.isArray(category.retentionFields) &&
|
||||
category.retentionFields.length > 0
|
||||
) {
|
||||
return (
|
||||
<Col flex="40%" style={{ minWidth: 475 }} key={category.name}>
|
||||
<Typography.Title level={3}>{category.name}</Typography.Title>
|
||||
|
||||
{category.retentionFields.map((retentionField) => (
|
||||
<Retention
|
||||
key={retentionField.name}
|
||||
text={retentionField.name}
|
||||
retentionValue={retentionField.value}
|
||||
setRetentionValue={retentionField.setValue}
|
||||
hide={!!retentionField.hide}
|
||||
/>
|
||||
))}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
if (trace === -1) {
|
||||
setIsDefaultTrace(true);
|
||||
} else {
|
||||
setIsDefaultTrace(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && payload !== undefined) {
|
||||
const {
|
||||
metrics_ttl_duration_hrs: metricTllDuration,
|
||||
traces_ttl_duration_hrs: traceTllDuration,
|
||||
} = payload;
|
||||
|
||||
checkMetricTraceDefault(traceTllDuration, metricTllDuration);
|
||||
|
||||
const traceValue = getSettingsPeroid(traceTllDuration);
|
||||
const metricsValue = getSettingsPeroid(metricTllDuration);
|
||||
|
||||
setRetentionPeroidTrace(traceValue.value.toString());
|
||||
setSelectedTracePeroid(traceValue.peroid);
|
||||
|
||||
setRetentionPeroidMetrics(metricsValue.value.toString());
|
||||
setSelectedMetricsPeroid(metricsValue.peroid);
|
||||
}
|
||||
}, [setSelectedMetricsPeroid, loading, payload]);
|
||||
return null;
|
||||
});
|
||||
|
||||
const onOkHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setPostApiLoading(true);
|
||||
const retentionTraceValue =
|
||||
retentionPeroidTrace === '0' && (payload?.traces_ttl_duration_hrs || 0) < 0
|
||||
? payload?.traces_ttl_duration_hrs || 0
|
||||
: parseInt(retentionPeroidTrace, 10);
|
||||
|
||||
const retentionMetricsValue =
|
||||
retentionPeroidMetrics === '0' &&
|
||||
(payload?.metrics_ttl_duration_hrs || 0) < 0
|
||||
? payload?.metrics_ttl_duration_hrs || 0
|
||||
: parseInt(retentionPeroidMetrics, 10);
|
||||
|
||||
const [tracesResponse, metricsResponse] = await Promise.all([
|
||||
setRetentionApi({
|
||||
duration: `${convertIntoHr(retentionTraceValue, selectedTracePeroid)}h`,
|
||||
type: 'traces',
|
||||
}),
|
||||
setRetentionApi({
|
||||
duration: `${convertIntoHr(
|
||||
retentionMetricsValue,
|
||||
selectedMetricsPeroid,
|
||||
)}h`,
|
||||
type: 'metrics',
|
||||
}),
|
||||
]);
|
||||
const apiCalls = [];
|
||||
|
||||
if (
|
||||
tracesResponse.statusCode === 200 &&
|
||||
metricsResponse.statusCode === 200
|
||||
!(
|
||||
currentTTLValues?.metrics_move_ttl_duration_hrs ===
|
||||
metricsS3RetentionPeriod &&
|
||||
currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod
|
||||
)
|
||||
) {
|
||||
notifications.success({
|
||||
message: 'Success!',
|
||||
placement: 'topRight',
|
||||
description: 'Congrats. The retention periods were updated correctly.',
|
||||
});
|
||||
|
||||
checkMetricTraceDefault(retentionTraceValue, retentionMetricsValue);
|
||||
|
||||
onModalToggleHandler();
|
||||
apiCalls.push(() =>
|
||||
setRetentionApi({
|
||||
type: 'metrics',
|
||||
totalDuration: `${metricsTotalRetentionPeriod || -1}h`,
|
||||
coldStorage: s3Enabled ? 's3' : null,
|
||||
toColdDuration: `${metricsS3RetentionPeriod || -1}h`,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description:
|
||||
'There was an issue in changing the retention period. Please try again or reach out to support@signoz.io',
|
||||
placement: 'topRight',
|
||||
});
|
||||
apiCalls.push(() => Promise.resolve(null));
|
||||
}
|
||||
|
||||
if (
|
||||
!(
|
||||
currentTTLValues?.traces_move_ttl_duration_hrs ===
|
||||
tracesS3RetentionPeriod &&
|
||||
currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod
|
||||
)
|
||||
) {
|
||||
apiCalls.push(() =>
|
||||
setRetentionApi({
|
||||
type: 'traces',
|
||||
totalDuration: `${tracesTotalRetentionPeriod || -1}h`,
|
||||
coldStorage: s3Enabled ? 's3' : null,
|
||||
toColdDuration: `${tracesS3RetentionPeriod || -1}h`,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
apiCalls.push(() => Promise.resolve(null));
|
||||
}
|
||||
const apiCallSequence = ['metrics', 'traces'];
|
||||
const apiResponses = await Promise.all(apiCalls.map((api) => api()));
|
||||
|
||||
apiResponses.forEach((apiResponse, idx) => {
|
||||
const name = apiCallSequence[idx];
|
||||
if (apiResponse) {
|
||||
if (apiResponse.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: 'Success!',
|
||||
placement: 'topRight',
|
||||
|
||||
description: t('settings.retention_success_message', { name }),
|
||||
});
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('settings.retention_error_message', { name }),
|
||||
placement: 'topRight',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
onModalToggleHandler();
|
||||
setPostApiLoading(false);
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description:
|
||||
'There was an issue in changing the retention period. Please try again or reach out to support@signoz.io',
|
||||
description: t('settings.retention_failed_message'),
|
||||
placement: 'topRight',
|
||||
});
|
||||
}
|
||||
// Updates the currentTTL Values in order to avoid pushing the same values.
|
||||
setCurrentTTLValues({
|
||||
metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1,
|
||||
metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1,
|
||||
traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1,
|
||||
traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1,
|
||||
});
|
||||
|
||||
setModal(false);
|
||||
};
|
||||
|
||||
const [isDisabled, errorText] = useMemo((): [boolean, string] => {
|
||||
// Various methods to return dynamic error message text.
|
||||
const messages = {
|
||||
compareError: (name: string | number): string =>
|
||||
t('settings.retention_comparison_error', { name }),
|
||||
nullValueError: (name: string | number): string =>
|
||||
t('settings.retention_null_value_error', { name }),
|
||||
};
|
||||
|
||||
// Defaults to button not disabled and empty error message text.
|
||||
let isDisabled = false;
|
||||
let errorText = '';
|
||||
|
||||
if (s3Enabled) {
|
||||
if (
|
||||
(metricsTotalRetentionPeriod || metricsS3RetentionPeriod) &&
|
||||
Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod)
|
||||
) {
|
||||
isDisabled = true;
|
||||
errorText = messages.compareError('metrics');
|
||||
} else if (
|
||||
(tracesTotalRetentionPeriod || tracesS3RetentionPeriod) &&
|
||||
Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod)
|
||||
) {
|
||||
isDisabled = true;
|
||||
errorText = messages.compareError('traces');
|
||||
}
|
||||
}
|
||||
|
||||
if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) {
|
||||
isDisabled = true;
|
||||
if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) {
|
||||
errorText = messages.nullValueError('metrics and traces');
|
||||
} else if (!metricsTotalRetentionPeriod) {
|
||||
errorText = messages.nullValueError('metrics');
|
||||
} else if (!tracesTotalRetentionPeriod) {
|
||||
errorText = messages.nullValueError('traces');
|
||||
}
|
||||
}
|
||||
if (
|
||||
currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod &&
|
||||
currentTTLValues.metrics_move_ttl_duration_hrs ===
|
||||
metricsS3RetentionPeriod &&
|
||||
currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod &&
|
||||
currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod
|
||||
) {
|
||||
isDisabled = true;
|
||||
}
|
||||
return [isDisabled, errorText];
|
||||
}, [
|
||||
currentTTLValues,
|
||||
metricsS3RetentionPeriod,
|
||||
metricsTotalRetentionPeriod,
|
||||
s3Enabled,
|
||||
t,
|
||||
tracesS3RetentionPeriod,
|
||||
tracesTotalRetentionPeriod,
|
||||
]);
|
||||
|
||||
if (error) {
|
||||
return <Typography>{errorMessage}</Typography>;
|
||||
}
|
||||
|
||||
if (loading || payload === undefined) {
|
||||
if (loading || currentTTLValues === undefined) {
|
||||
return <Spinner tip="Loading.." height="70vh" />;
|
||||
}
|
||||
|
||||
const getErrorText = (): string => {
|
||||
const getValue = (value: string): string =>
|
||||
`Retention Peroid for ${value} is not set yet. Please set by choosing below`;
|
||||
|
||||
if (!isDefaultMetrics && !isDefaultTrace) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isDefaultMetrics && !isDefaultTrace) {
|
||||
return `${getValue('Metrics')}`;
|
||||
}
|
||||
|
||||
if (!isDefaultMetrics && isDefaultTrace) {
|
||||
return `${getValue('Trace')}`;
|
||||
}
|
||||
|
||||
return `${getValue('Trace , Metrics')}`;
|
||||
};
|
||||
|
||||
const isDisabledHandler = (): boolean => {
|
||||
return !!(retentionPeroidTrace === '' || retentionPeroidMetrics === '');
|
||||
};
|
||||
|
||||
const errorText = getErrorText();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
||||
{Element}
|
||||
<ErrorTextContainer>
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to set retention period`,
|
||||
url: 'https://signoz.io/docs/userguide/retention-period/',
|
||||
}}
|
||||
/>
|
||||
{errorText && <ErrorText>{errorText}</ErrorText>}
|
||||
</ErrorTextContainer>
|
||||
|
||||
{errorText ? (
|
||||
<ErrorTextContainer>
|
||||
<ErrorText>{errorText}</ErrorText>
|
||||
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to set retention period`,
|
||||
url: 'https://signoz.io/docs/userguide/retention-period/',
|
||||
}}
|
||||
/>
|
||||
</ErrorTextContainer>
|
||||
) : (
|
||||
<ToolTipContainer>
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to set retention period`,
|
||||
url: 'https://signoz.io/docs/userguide/retention-period/',
|
||||
}}
|
||||
/>
|
||||
</ToolTipContainer>
|
||||
)}
|
||||
|
||||
<Retention
|
||||
text="Retention Period for Metrics"
|
||||
selectedRetentionPeroid={selectedMetricsPeroid}
|
||||
setRentionValue={setRetentionPeroidMetrics}
|
||||
retentionValue={retentionPeroidMetrics}
|
||||
setSelectedRetentionPeroid={setSelectedMetricsPeroid}
|
||||
/>
|
||||
|
||||
<Retention
|
||||
text="Retention Period for Traces"
|
||||
selectedRetentionPeroid={selectedTracePeroid}
|
||||
setRentionValue={setRetentionPeroidTrace}
|
||||
retentionValue={retentionPeroidTrace}
|
||||
setSelectedRetentionPeroid={setSelectedTracePeroid}
|
||||
/>
|
||||
<Row justify="space-around">{renderConfig}</Row>
|
||||
|
||||
<Modal
|
||||
title="Are you sure you want to change the retention period?"
|
||||
title={t('settings.retention_confirmation')}
|
||||
focusTriggerAfterClose
|
||||
forceRender
|
||||
destroyOnClose
|
||||
@ -233,24 +317,18 @@ function GeneralSettings(): JSX.Element {
|
||||
visible={modal}
|
||||
confirmLoading={postApiLoading}
|
||||
>
|
||||
<Typography>
|
||||
This will change the amount of storage needed for saving metrics & traces.
|
||||
</Typography>
|
||||
<Typography>{t('settings.retention_confirmation_description')}</Typography>
|
||||
</Modal>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
onClick={onClickSaveHandler}
|
||||
disabled={isDisabledHandler()}
|
||||
type="primary"
|
||||
>
|
||||
<Button onClick={onClickSaveHandler} disabled={isDisabled} type="primary">
|
||||
Save
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export type SettingPeroid = 'hr' | 'day' | 'month';
|
||||
export type SettingPeriod = 'hr' | 'day' | 'month';
|
||||
|
||||
export default GeneralSettings;
|
||||
|
@ -1,16 +1,13 @@
|
||||
import {
|
||||
Col,
|
||||
Dropdown as DropDownComponent,
|
||||
Input as InputComponent,
|
||||
Typography as TypographyComponent,
|
||||
} from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const RetentionContainer = styled.div`
|
||||
width: 50%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
export const RetentionContainer = styled(Col)`
|
||||
margin: 0.75rem 0;
|
||||
`;
|
||||
|
||||
export const Input = styled(InputComponent)`
|
||||
@ -37,13 +34,6 @@ export const ButtonContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Dropdown = styled(DropDownComponent)`
|
||||
&&& {
|
||||
display: flex;
|
||||
@ -66,6 +56,7 @@ export const ErrorTextContainer = styled.div`
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
> article {
|
||||
margin-right: 1rem;
|
||||
@ -90,3 +81,12 @@ export const ErrorText = styled(TypographyComponent)`
|
||||
font-style: italic;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RetentionFieldLabel = styled(TypographyComponent)`
|
||||
vertical-align: middle;
|
||||
white-space: pre-wrap;
|
||||
`;
|
||||
|
||||
export const RetentionFieldInputContainer = styled.div`
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
42
frontend/src/container/GeneralSettings/utils.ts
Normal file
42
frontend/src/container/GeneralSettings/utils.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export type SettingPeriod = 'hr' | 'day' | 'month';
|
||||
|
||||
export interface ITimeUnit {
|
||||
value: SettingPeriod;
|
||||
key: string;
|
||||
multiplier: number;
|
||||
}
|
||||
export const TimeUnits: ITimeUnit[] = [
|
||||
{
|
||||
value: 'hr',
|
||||
key: 'Hours',
|
||||
multiplier: 1,
|
||||
},
|
||||
{
|
||||
value: 'day',
|
||||
key: 'Days',
|
||||
multiplier: 1 / 24,
|
||||
},
|
||||
{
|
||||
value: 'month',
|
||||
key: 'Months',
|
||||
multiplier: 1 / (24 * 30),
|
||||
},
|
||||
];
|
||||
|
||||
export const convertHoursValueToRelevantUnit = (
|
||||
value: number,
|
||||
): { value: number; timeUnitValue: SettingPeriod } => {
|
||||
if (value)
|
||||
for (let idx = TimeUnits.length - 1; idx >= 0; idx -= 1) {
|
||||
const timeUnit = TimeUnits[idx];
|
||||
const convertedValue = timeUnit.multiplier * value;
|
||||
|
||||
if (
|
||||
convertedValue >= 1 &&
|
||||
convertedValue === parseInt(`${convertedValue}`, 10)
|
||||
) {
|
||||
return { value: convertedValue, timeUnitValue: timeUnit.value };
|
||||
}
|
||||
}
|
||||
return { value, timeUnitValue: TimeUnits[0].value };
|
||||
};
|
@ -1,91 +0,0 @@
|
||||
import Graph, { GraphOnClickHandler } from 'components/Graph';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
function EmptyGraph({
|
||||
selectedTime,
|
||||
widget,
|
||||
onClickHandler,
|
||||
}: EmptyGraphProps): JSX.Element {
|
||||
const { minTime, maxTime, loading } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const maxMinTime = GetMaxMinTime({
|
||||
graphType: widget.panelTypes,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
|
||||
const { end, start } = getStartAndEndTime({
|
||||
type: selectedTime.enum,
|
||||
maxTime: maxMinTime.maxTime,
|
||||
minTime: maxMinTime.minTime,
|
||||
});
|
||||
|
||||
const dateFunction = useCallback(() => {
|
||||
if (!loading) {
|
||||
const dates: Date[] = [];
|
||||
|
||||
const startString = getTimeString(start);
|
||||
const endString = getTimeString(end);
|
||||
|
||||
const parsedStart = parseInt(startString, 10);
|
||||
const parsedEnd = parseInt(endString, 10);
|
||||
|
||||
let startDate = parsedStart;
|
||||
const endDate = parsedEnd;
|
||||
|
||||
while (endDate >= startDate) {
|
||||
const newDate = new Date(startDate);
|
||||
|
||||
startDate += 20000;
|
||||
|
||||
dates.push(newDate);
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
return [];
|
||||
}, [start, end, loading]);
|
||||
|
||||
const date = dateFunction();
|
||||
|
||||
return (
|
||||
<Graph
|
||||
name=""
|
||||
{...{
|
||||
type: 'line',
|
||||
onClickHandler,
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
data: new Array(date?.length).fill(0),
|
||||
borderColor: colors[0],
|
||||
showLine: true,
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
pointRadius: 0,
|
||||
},
|
||||
],
|
||||
labels: date,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface EmptyGraphProps {
|
||||
selectedTime: timePreferance;
|
||||
widget: Widgets;
|
||||
onClickHandler: GraphOnClickHandler | undefined;
|
||||
}
|
||||
|
||||
export default EmptyGraph;
|
@ -23,14 +23,12 @@ import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import EmptyGraph from './EmptyGraph';
|
||||
import { NotFoundContainer, TimeContainer } from './styles';
|
||||
|
||||
function FullView({
|
||||
widget,
|
||||
fullViewOptions = true,
|
||||
onClickHandler,
|
||||
noDataGraph = false,
|
||||
name,
|
||||
yAxisUnit,
|
||||
}: FullViewProps): JSX.Element {
|
||||
@ -166,38 +164,6 @@ function FullView({
|
||||
);
|
||||
}
|
||||
|
||||
if (state.loading === false && state.payload.datasets.length === 0) {
|
||||
return (
|
||||
<>
|
||||
{fullViewOptions && (
|
||||
<TimeContainer>
|
||||
<TimePreference
|
||||
{...{
|
||||
selectedTime,
|
||||
setSelectedTime,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={onFetchDataHandler} type="primary">
|
||||
Refresh
|
||||
</Button>
|
||||
</TimeContainer>
|
||||
)}
|
||||
|
||||
{noDataGraph ? (
|
||||
<EmptyGraph
|
||||
onClickHandler={onClickHandler}
|
||||
widget={widget}
|
||||
selectedTime={selectedTime}
|
||||
/>
|
||||
) : (
|
||||
<NotFoundContainer>
|
||||
<Typography>No Data</Typography>
|
||||
</NotFoundContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{fullViewOptions && (
|
||||
@ -243,7 +209,6 @@ interface FullViewProps {
|
||||
widget: Widgets;
|
||||
fullViewOptions?: boolean;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
noDataGraph?: boolean;
|
||||
name: string;
|
||||
yAxisUnit?: string;
|
||||
}
|
||||
@ -251,7 +216,6 @@ interface FullViewProps {
|
||||
FullView.defaultProps = {
|
||||
fullViewOptions: undefined,
|
||||
onClickHandler: undefined,
|
||||
noDataGraph: undefined,
|
||||
yAxisUnit: undefined,
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@ const breadcrumbNameMap = {
|
||||
[ROUTES.INSTRUMENTATION]: 'Add instrumentation',
|
||||
[ROUTES.SETTINGS]: 'Settings',
|
||||
[ROUTES.DASHBOARD]: 'Dashboard',
|
||||
[ROUTES.VERSION]: 'Status',
|
||||
};
|
||||
|
||||
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||
import { Modal } from 'antd';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
@ -8,14 +10,29 @@ import AppActions from 'types/actions';
|
||||
import { Data } from '../index';
|
||||
import { TableLinkText } from './styles';
|
||||
|
||||
function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element {
|
||||
const onClickHandler = useCallback(() => {
|
||||
deleteDashboard({
|
||||
uuid: id,
|
||||
});
|
||||
}, [id, deleteDashboard]);
|
||||
const { confirm } = Modal;
|
||||
|
||||
return <TableLinkText onClick={onClickHandler}>Delete</TableLinkText>;
|
||||
function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element {
|
||||
const openConfirmationDialog = (): void => {
|
||||
confirm({
|
||||
title: 'Do you really want to delete this dashboard?',
|
||||
icon: <ExclamationCircleOutlined style={{ color: '#e42b35' }} />,
|
||||
onOk() {
|
||||
deleteDashboard({
|
||||
uuid: id,
|
||||
});
|
||||
},
|
||||
okText: 'Delete',
|
||||
okButtonProps: { danger: true },
|
||||
centered: true,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<TableLinkText type="danger" onClick={openConfirmationDialog}>
|
||||
Delete
|
||||
</TableLinkText>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -157,7 +157,7 @@ function ListOfAllDashboard(): JSX.Element {
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to create dashboards`,
|
||||
url: 'https://signoz.io/docs/userguide/metrics-dashboard',
|
||||
url: 'https://signoz.io/docs/userguide/dashboards',
|
||||
}}
|
||||
/>
|
||||
|
||||
|
@ -36,7 +36,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
history.replace(
|
||||
`${
|
||||
ROUTES.TRACE
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["ok","error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`,
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=[]&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
|
||||
);
|
||||
};
|
||||
|
||||
@ -88,7 +88,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
history.replace(
|
||||
`${
|
||||
ROUTES.TRACE
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`,
|
||||
}?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=[]&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
|
||||
);
|
||||
};
|
||||
|
||||
@ -179,7 +179,6 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="request_per_sec"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(event, element, chart, data): void => {
|
||||
onClickhandler(event, element, chart, data, 'Request');
|
||||
@ -214,7 +213,6 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="error_percentage_%"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onClickhandler(ChartEvent, activeElements, chart, data, 'Error');
|
||||
|
@ -17,7 +17,6 @@ function DBCall({ getWidget }: DBCallProps): JSX.Element {
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="database_call_rps"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
{
|
||||
@ -37,7 +36,6 @@ function DBCall({ getWidget }: DBCallProps): JSX.Element {
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="database_call_avg_duration"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
{
|
||||
|
@ -21,7 +21,6 @@ function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
<FullView
|
||||
name="external_call_error_percentage"
|
||||
fullViewOptions={false}
|
||||
noDataGraph
|
||||
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)`,
|
||||
@ -40,7 +39,6 @@ function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="external_call_duration"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
{
|
||||
@ -62,7 +60,6 @@ function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
name="external_call_rps_by_address"
|
||||
noDataGraph
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
{
|
||||
@ -81,7 +78,6 @@ function External({ getWidget }: ExternalProps): JSX.Element {
|
||||
<GraphTitle>External Call duration(by Address)</GraphTitle>
|
||||
<GraphContainer>
|
||||
<FullView
|
||||
noDataGraph
|
||||
name="external_call_duration_by_address"
|
||||
fullViewOptions={false}
|
||||
widget={getWidget([
|
||||
|
@ -33,7 +33,7 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
|
||||
history.push(
|
||||
`${
|
||||
ROUTES.TRACE
|
||||
}?${urlParams.toString()}&selected={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&isSelectedFilterSkipped=true&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&isSelectedFilterSkipped=true`,
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=[]&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -114,7 +114,7 @@ function Query({
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to plot metrics graphs`,
|
||||
url: 'https://signoz.io/docs/userguide/prometheus-metrics/',
|
||||
url: 'https://signoz.io/docs/userguide/send-metrics/#related-videos',
|
||||
}}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons';
|
||||
import { Menu, Typography } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||
@ -5,6 +6,7 @@ import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import setTheme, { AppMode } from 'lib/theme/setTheme';
|
||||
import React, { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import { bindActionCreators } from 'redux';
|
||||
@ -19,11 +21,13 @@ import menus from './menuItems';
|
||||
import Slack from './Slack';
|
||||
import {
|
||||
Logo,
|
||||
RedDot,
|
||||
Sider,
|
||||
SlackButton,
|
||||
SlackMenuItemContainer,
|
||||
ThemeSwitcherWrapper,
|
||||
ToggleButton,
|
||||
VersionContainer,
|
||||
} from './styles';
|
||||
|
||||
function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
@ -31,9 +35,15 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
const [collapsed, setCollapsed] = useState<boolean>(
|
||||
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||
);
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const {
|
||||
isDarkMode,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
isCurrentVersionError,
|
||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation('');
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode';
|
||||
@ -77,6 +87,38 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
window.open('https://signoz.io/slack', '_blank');
|
||||
};
|
||||
|
||||
const onClickVersionHandler = (): void => {
|
||||
history.push(ROUTES.VERSION);
|
||||
};
|
||||
|
||||
const isNotCurrentVersion = currentVersion !== latestVersion;
|
||||
|
||||
const sidebar = [
|
||||
{
|
||||
onClick: onClickSlackHandler,
|
||||
icon: <Slack />,
|
||||
text: <SlackButton>Support</SlackButton>,
|
||||
},
|
||||
{
|
||||
onClick: onClickVersionHandler,
|
||||
icon: isNotCurrentVersion ? (
|
||||
<WarningOutlined style={{ color: '#E87040' }} />
|
||||
) : (
|
||||
<CheckCircleTwoTone twoToneColor={['#D5F2BB', '#1f1f1f']} />
|
||||
),
|
||||
text: (
|
||||
<VersionContainer>
|
||||
{!isCurrentVersionError ? (
|
||||
<SlackButton>{currentVersion}</SlackButton>
|
||||
) : (
|
||||
<SlackButton>{t('n_a')}</SlackButton>
|
||||
)}
|
||||
{isNotCurrentVersion && <RedDot />}
|
||||
</VersionContainer>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}>
|
||||
<ThemeSwitcherWrapper>
|
||||
@ -87,7 +129,7 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
/>
|
||||
</ThemeSwitcherWrapper>
|
||||
<NavLink to={ROUTES.APPLICATION}>
|
||||
<Logo src="/signoz.svg" alt="SigNoz" collapsed={collapsed} />
|
||||
<Logo index={0} src="/signoz.svg" alt="SigNoz" collapsed={collapsed} />
|
||||
</NavLink>
|
||||
|
||||
<Menu
|
||||
@ -105,11 +147,21 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
|
||||
<Typography>{name}</Typography>
|
||||
</Menu.Item>
|
||||
))}
|
||||
<SlackMenuItemContainer collapsed={collapsed}>
|
||||
<Menu.Item onClick={onClickSlackHandler} icon={<Slack />}>
|
||||
<SlackButton>Support</SlackButton>
|
||||
</Menu.Item>
|
||||
</SlackMenuItemContainer>
|
||||
{sidebar.map((props, index) => (
|
||||
<SlackMenuItemContainer
|
||||
index={index + 1}
|
||||
key={`${index + 1}`}
|
||||
collapsed={collapsed}
|
||||
>
|
||||
<Menu.Item
|
||||
eventKey={index.toString()}
|
||||
onClick={props.onClick}
|
||||
icon={props.icon}
|
||||
>
|
||||
{props.text}
|
||||
</Menu.Item>
|
||||
</SlackMenuItemContainer>
|
||||
))}
|
||||
</Menu>
|
||||
</Sider>
|
||||
);
|
||||
|
@ -19,6 +19,7 @@ export const Logo = styled.img<LogoProps>`
|
||||
|
||||
interface LogoProps {
|
||||
collapsed: boolean;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const Sider = styled(SiderComponent)`
|
||||
@ -50,9 +51,10 @@ export const SlackButton = styled(Typography)`
|
||||
|
||||
export const SlackMenuItemContainer = styled.div<LogoProps>`
|
||||
position: fixed;
|
||||
bottom: 48px;
|
||||
bottom: ${({ index }): string => `${index * 48 + (index + 16)}px`};
|
||||
background: #262626;
|
||||
width: ${({ collapsed }): string => (!collapsed ? '200px' : '80px')};
|
||||
transition: inherit;
|
||||
|
||||
&&& {
|
||||
li {
|
||||
@ -60,11 +62,14 @@ export const SlackMenuItemContainer = styled.div<LogoProps>`
|
||||
collapsed &&
|
||||
css`
|
||||
padding-left: 24px;
|
||||
padding-top: 6px;
|
||||
`}
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-left: ${({ collapsed }): string => (collapsed ? '0' : '24px')};
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
${({ collapsed }): StyledCSS =>
|
||||
collapsed &&
|
||||
@ -73,5 +78,24 @@ export const SlackMenuItemContainer = styled.div<LogoProps>`
|
||||
margin: 0 auto;
|
||||
`}
|
||||
}
|
||||
.ant-menu-title-content {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RedDot = styled.div`
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #d32029;
|
||||
border-radius: 50%;
|
||||
|
||||
margin-left: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
`;
|
||||
|
||||
export const VersionContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
@ -93,11 +93,15 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
if (response.statusCode === 200) {
|
||||
const updatedFilter = getFilter(response.payload);
|
||||
|
||||
updatedFilter.forEach((value, key) => {
|
||||
if (key !== 'duration' && name !== key) {
|
||||
preUserSelectedMap.set(key, Object.keys(value));
|
||||
}
|
||||
});
|
||||
// updatedFilter.forEach((value, key) => {
|
||||
// if (key !== 'duration' && name !== key) {
|
||||
// preUserSelectedMap.set(key, Object.keys(value));
|
||||
// }
|
||||
|
||||
// if (key === 'duration') {
|
||||
// newSelectedMap.set('duration', [value.maxDuration, value.minDuration]);
|
||||
// }
|
||||
// });
|
||||
|
||||
updatedFilter.set(name, {
|
||||
[`${keyValue}`]: '-1',
|
||||
@ -115,6 +119,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
selectedFilter: newSelectedMap,
|
||||
userSelected: preUserSelectedMap,
|
||||
isFilterExclude: preIsFilterExclude,
|
||||
order: spansAggregate.order,
|
||||
},
|
||||
});
|
||||
|
||||
@ -125,9 +130,9 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
filterToFetchData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
updatedFilter,
|
||||
preIsFilterExclude,
|
||||
preUserSelectedMap,
|
||||
spansAggregate.order,
|
||||
);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
|
@ -18,16 +18,26 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{statusObj.map((e) => (
|
||||
<CheckBoxComponent
|
||||
key={e}
|
||||
{...{
|
||||
name,
|
||||
keyValue: e,
|
||||
value: status[e],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{statusObj
|
||||
.sort((a, b) => {
|
||||
const countA = +status[a];
|
||||
const countB = +status[b];
|
||||
|
||||
if (countA === countB) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
return countA - countB;
|
||||
})
|
||||
.map((e) => (
|
||||
<CheckBoxComponent
|
||||
key={e}
|
||||
{...{
|
||||
name,
|
||||
keyValue: e,
|
||||
value: status[e],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import getFilters from 'api/trace/getFilters';
|
||||
import dayjs from 'dayjs';
|
||||
import durationPlugin from 'dayjs/plugin/duration';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { getFilter, updateURL } from 'store/actions/trace/util';
|
||||
@ -20,11 +20,13 @@ import { Container, InputContainer, Text } from './styles';
|
||||
dayjs.extend(durationPlugin);
|
||||
|
||||
const getMs = (value: string): string => {
|
||||
return dayjs
|
||||
.duration({
|
||||
milliseconds: parseInt(value, 10) / 1000000,
|
||||
})
|
||||
.format('SSS');
|
||||
return parseFloat(
|
||||
dayjs
|
||||
.duration({
|
||||
milliseconds: parseInt(value, 10) / 1000000,
|
||||
})
|
||||
.format('SSS'),
|
||||
).toFixed(2);
|
||||
};
|
||||
|
||||
function Duration(): JSX.Element {
|
||||
@ -43,9 +45,10 @@ function Duration(): JSX.Element {
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const getDuration = ():
|
||||
| { maxDuration: string; minDuration: string }
|
||||
| Record<string, string> => {
|
||||
const preLocalMaxDuration = useRef<number>();
|
||||
const preLocalMinDuration = useRef<number>();
|
||||
|
||||
const getDuration = useMemo(() => {
|
||||
const selectedDuration = selectedFilter.get('duration');
|
||||
|
||||
if (selectedDuration) {
|
||||
@ -56,17 +59,29 @@ function Duration(): JSX.Element {
|
||||
}
|
||||
|
||||
return filter.get('duration') || {};
|
||||
};
|
||||
}, [selectedFilter, filter]);
|
||||
|
||||
const duration = getDuration();
|
||||
const [preMax, setPreMax] = useState<string>('');
|
||||
const [preMin, setPreMin] = useState<string>('');
|
||||
|
||||
const maxDuration = duration.maxDuration || '0';
|
||||
const minDuration = duration.minDuration || '0';
|
||||
useEffect(() => {
|
||||
const duration = getDuration || {};
|
||||
|
||||
const [localMax, setLocalMax] = useState<string>(maxDuration);
|
||||
const [localMin, setLocalMin] = useState<string>(minDuration);
|
||||
const maxDuration = duration.maxDuration || '0';
|
||||
const minDuration = duration.minDuration || '0';
|
||||
|
||||
const defaultValue = [parseFloat(minDuration), parseFloat(maxDuration)];
|
||||
if (preLocalMaxDuration.current === undefined) {
|
||||
preLocalMaxDuration.current = parseFloat(maxDuration);
|
||||
}
|
||||
if (preLocalMinDuration.current === undefined) {
|
||||
preLocalMinDuration.current = parseFloat(minDuration);
|
||||
}
|
||||
|
||||
setPreMax(maxDuration);
|
||||
setPreMin(minDuration);
|
||||
}, [getDuration]);
|
||||
|
||||
const defaultValue = [parseFloat(preMin), parseFloat(preMax)];
|
||||
|
||||
const updatedUrl = async (min: number, max: number): Promise<void> => {
|
||||
const preSelectedFilter = new Map(selectedFilter);
|
||||
@ -74,7 +89,6 @@ function Duration(): JSX.Element {
|
||||
|
||||
preSelectedFilter.set('duration', [String(max), String(min)]);
|
||||
|
||||
console.log('on the update Url');
|
||||
const response = await getFilters({
|
||||
end: String(globalTime.maxTime),
|
||||
getFilters: filterToFetchData,
|
||||
@ -87,8 +101,9 @@ function Duration(): JSX.Element {
|
||||
const preFilter = getFilter(response.payload);
|
||||
|
||||
preFilter.forEach((value, key) => {
|
||||
if (key !== 'duration') {
|
||||
preUserSelected.set(key, Object.keys(value));
|
||||
const values = Object.keys(value);
|
||||
if (key !== 'duration' && values.length) {
|
||||
preUserSelected.set(key, values);
|
||||
}
|
||||
});
|
||||
|
||||
@ -102,6 +117,7 @@ function Duration(): JSX.Element {
|
||||
selectedTags,
|
||||
userSelected: preUserSelected,
|
||||
isFilterExclude,
|
||||
order: spansAggregate.order,
|
||||
},
|
||||
});
|
||||
|
||||
@ -110,9 +126,9 @@ function Duration(): JSX.Element {
|
||||
filterToFetchData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
preFilter,
|
||||
isFilterExclude,
|
||||
userSelectedFilter,
|
||||
spansAggregate.order,
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -120,13 +136,12 @@ function Duration(): JSX.Element {
|
||||
const onRangeSliderHandler = (number: [number, number]): void => {
|
||||
const [min, max] = number;
|
||||
|
||||
setLocalMin(min.toString());
|
||||
setLocalMax(max.toString());
|
||||
setPreMin(min.toString());
|
||||
setPreMax(max.toString());
|
||||
};
|
||||
|
||||
const debouncedFunction = useDebouncedFn(
|
||||
(min, max) => {
|
||||
console.log('debounce function');
|
||||
updatedUrl(min as number, max as number);
|
||||
},
|
||||
500,
|
||||
@ -137,11 +152,9 @@ function Duration(): JSX.Element {
|
||||
event,
|
||||
) => {
|
||||
const { value } = event.target;
|
||||
const min = parseFloat(localMin);
|
||||
const min = parseFloat(preMin);
|
||||
const max = parseFloat(value) * 1000000;
|
||||
|
||||
console.log('on change in max');
|
||||
|
||||
onRangeSliderHandler([min, max]);
|
||||
debouncedFunction(min, max);
|
||||
};
|
||||
@ -151,9 +164,8 @@ function Duration(): JSX.Element {
|
||||
) => {
|
||||
const { value } = event.target;
|
||||
const min = parseFloat(value) * 1000000;
|
||||
const max = parseFloat(localMax);
|
||||
const max = parseFloat(preMax);
|
||||
onRangeSliderHandler([min, max]);
|
||||
console.log('on change in min');
|
||||
debouncedFunction(min, max);
|
||||
};
|
||||
|
||||
@ -170,7 +182,7 @@ function Duration(): JSX.Element {
|
||||
<Input
|
||||
addonAfter="ms"
|
||||
onChange={onChangeMinHandler}
|
||||
value={getMs(localMin)}
|
||||
value={getMs(preMin)}
|
||||
/>
|
||||
|
||||
<InputContainer>
|
||||
@ -179,27 +191,27 @@ function Duration(): JSX.Element {
|
||||
<Input
|
||||
addonAfter="ms"
|
||||
onChange={onChangeMaxHandler}
|
||||
value={getMs(localMax)}
|
||||
value={getMs(preMax)}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
<Container>
|
||||
<Slider
|
||||
defaultValue={[defaultValue[0], defaultValue[1]]}
|
||||
min={parseFloat((filter.get('duration') || {}).minDuration)}
|
||||
max={parseFloat((filter.get('duration') || {}).maxDuration)}
|
||||
min={parseFloat((preLocalMinDuration.current || 0).toString())}
|
||||
max={parseFloat((preLocalMaxDuration.current || 0).toString())}
|
||||
range
|
||||
tipFormatter={(value): JSX.Element => {
|
||||
if (value === undefined) {
|
||||
return <div />;
|
||||
}
|
||||
return <div>{`${getMs(value.toString())}ms`}</div>;
|
||||
return <div>{`${getMs(value?.toString())}ms`}</div>;
|
||||
}}
|
||||
onChange={([min, max]): void => {
|
||||
onRangeSliderHandler([min, max]);
|
||||
}}
|
||||
onAfterChange={onRangeHandler}
|
||||
value={[parseFloat(localMin), parseFloat(localMax)]}
|
||||
value={[parseFloat(preMin), parseFloat(preMax)]}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
ButtonContainer,
|
||||
Container,
|
||||
IconContainer,
|
||||
TextCotainer,
|
||||
TextContainer,
|
||||
} from './styles';
|
||||
|
||||
const { Text } = Typography;
|
||||
@ -64,16 +64,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
const getprepdatedSelectedFilter = new Map(selectedFilter);
|
||||
const getPreUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
if (!isDefaultOpen) {
|
||||
updatedFilterData = [PanelName];
|
||||
} else {
|
||||
// removing the selected filter
|
||||
updatedFilterData = [
|
||||
...filterToFetchData.filter((name) => name !== PanelName),
|
||||
];
|
||||
getprepdatedSelectedFilter.delete(PanelName);
|
||||
getPreUserSelected.delete(PanelName);
|
||||
}
|
||||
updatedFilterData = [PanelName];
|
||||
|
||||
const response = await getFilters({
|
||||
end: String(global.maxTime),
|
||||
@ -86,33 +77,14 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
if (response.statusCode === 200) {
|
||||
const updatedFilter = getFilter(response.payload);
|
||||
|
||||
// is closed
|
||||
if (!isDefaultOpen) {
|
||||
// getprepdatedSelectedFilter.set(
|
||||
// props.name,
|
||||
// Object.keys(updatedFilter.get(props.name) || {}),
|
||||
// );
|
||||
|
||||
if (!getPreUserSelected.has(PanelName)) {
|
||||
getPreUserSelected.set(
|
||||
PanelName,
|
||||
Object.keys(updatedFilter.get(PanelName) || {}),
|
||||
Object.keys(updatedFilter.get(PanelName) || []),
|
||||
);
|
||||
|
||||
updatedFilterData = [...filterToFetchData, PanelName];
|
||||
}
|
||||
|
||||
// now append the non prop.name trace filter enum over the list
|
||||
// selectedFilter.forEach((value, key) => {
|
||||
// if (key !== props.name) {
|
||||
// getprepdatedSelectedFilter.set(key, value);
|
||||
// }
|
||||
// });
|
||||
|
||||
getPreUserSelected.forEach((value, key) => {
|
||||
if (key !== PanelName) {
|
||||
getPreUserSelected.set(key, value);
|
||||
}
|
||||
});
|
||||
updatedFilterData = [...filterToFetchData, PanelName];
|
||||
filter.forEach((value, key) => {
|
||||
if (key !== PanelName) {
|
||||
updatedFilter.set(key, value);
|
||||
@ -129,6 +101,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
selectedTags,
|
||||
userSelected: getPreUserSelected,
|
||||
isFilterExclude,
|
||||
order: spansAggregate.order,
|
||||
},
|
||||
});
|
||||
|
||||
@ -137,9 +110,9 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
updatedFilterData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
updatedFilter,
|
||||
isFilterExclude,
|
||||
getPreUserSelected,
|
||||
spansAggregate.order,
|
||||
);
|
||||
} else {
|
||||
notification.error({
|
||||
@ -155,6 +128,43 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description this function removes the selected filter
|
||||
*/
|
||||
const onCloseHandler = (): void => {
|
||||
const preSelectedFilter = new Map(selectedFilter);
|
||||
// removing the filter from filter to fetch the data
|
||||
const preFilterToFetchTheData = [
|
||||
...filterToFetchData.filter((name) => name !== PanelName),
|
||||
];
|
||||
|
||||
// preSelectedFilter.delete(PanelName);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
current: spansAggregate.currentPage,
|
||||
filter,
|
||||
filterToFetchData: preFilterToFetchTheData,
|
||||
selectedFilter: preSelectedFilter,
|
||||
selectedTags,
|
||||
userSelected: userSelectedFilter,
|
||||
isFilterExclude,
|
||||
order: spansAggregate.order,
|
||||
},
|
||||
});
|
||||
|
||||
updateURL(
|
||||
preSelectedFilter,
|
||||
preFilterToFetchTheData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
isFilterExclude,
|
||||
userSelectedFilter,
|
||||
spansAggregate.order,
|
||||
);
|
||||
};
|
||||
|
||||
const onClearAllHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@ -177,18 +187,19 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
});
|
||||
|
||||
if (response.statusCode === 200 && response.payload) {
|
||||
const getUpatedFilter = getFilter(response.payload);
|
||||
const getUpdatedFilter = getFilter(response.payload);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
current: spansAggregate.currentPage,
|
||||
filter: getUpatedFilter,
|
||||
filter: getUpdatedFilter,
|
||||
filterToFetchData,
|
||||
selectedFilter: updatedFilter,
|
||||
selectedTags,
|
||||
userSelected: preUserSelected,
|
||||
isFilterExclude: postIsFilterExclude,
|
||||
order: spansAggregate.order,
|
||||
},
|
||||
});
|
||||
|
||||
@ -197,9 +208,9 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
filterToFetchData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
getUpatedFilter,
|
||||
postIsFilterExclude,
|
||||
preUserSelected,
|
||||
spansAggregate.order,
|
||||
);
|
||||
} else {
|
||||
notification.error({
|
||||
@ -280,7 +291,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
aria-disabled={filterLoading || isLoading}
|
||||
aria-expanded={IsPanelOpen}
|
||||
>
|
||||
<TextCotainer onClick={onExpandHandler}>
|
||||
<TextContainer onClick={isDefaultOpen ? onCloseHandler : onExpandHandler}>
|
||||
<IconContainer>
|
||||
{!IsPanelOpen ? <RightOutlined /> : <DownOutlined />}
|
||||
</IconContainer>
|
||||
@ -288,7 +299,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
|
||||
<Text style={{ textTransform: 'capitalize' }} ellipsis>
|
||||
{AllPanelHeading.find((e) => e.key === PanelName)?.displayValue || ''}
|
||||
</Text>
|
||||
</TextCotainer>
|
||||
</TextContainer>
|
||||
|
||||
{PanelName !== 'duration' && (
|
||||
<ButtonContainer>
|
||||
|
@ -30,7 +30,7 @@ export const IconContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const TextCotainer = styled.div`
|
||||
export const TextContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
70
frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx
Normal file
70
frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { Select } from 'antd';
|
||||
import { DefaultOptionType } from 'antd/lib/select';
|
||||
import getTagValue from 'api/trace/getTagValue';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { PayloadProps, Props } from 'types/api/trace/getTagValue';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { Value } from '.';
|
||||
import { SelectComponent } from './styles';
|
||||
|
||||
function TagValue(props: TagValueProps): JSX.Element {
|
||||
const { tag, setLocalSelectedTags, index, tagKey } = props;
|
||||
const {
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: selectedValues,
|
||||
} = tag;
|
||||
|
||||
const globalReducer = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const valueSuggestion = useFetch<PayloadProps, Props>(getTagValue, {
|
||||
end: globalReducer.maxTime,
|
||||
start: globalReducer.minTime,
|
||||
tagKey,
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectComponent
|
||||
value={selectedValues[0]}
|
||||
onSelect={(value: unknown): void => {
|
||||
if (typeof value === 'string') {
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: [...selectedValues, value],
|
||||
},
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
}
|
||||
}}
|
||||
loading={valueSuggestion.loading || false}
|
||||
>
|
||||
{valueSuggestion.payload &&
|
||||
valueSuggestion.payload.map((suggestion) => (
|
||||
<Select.Option key={suggestion.tagValues} value={suggestion.tagValues}>
|
||||
{suggestion.tagValues}
|
||||
</Select.Option>
|
||||
))}
|
||||
</SelectComponent>
|
||||
);
|
||||
}
|
||||
|
||||
interface TagValueProps {
|
||||
index: number;
|
||||
tag: FlatArray<TraceReducer['selectedTags'], 1>;
|
||||
setLocalSelectedTags: React.Dispatch<
|
||||
React.SetStateAction<TraceReducer['selectedTags']>
|
||||
>;
|
||||
tagKey: string;
|
||||
}
|
||||
|
||||
export default TagValue;
|
@ -5,13 +5,9 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import {
|
||||
Container,
|
||||
IconContainer,
|
||||
SelectComponent,
|
||||
ValueSelect,
|
||||
} from './styles';
|
||||
import { Container, IconContainer, SelectComponent } from './styles';
|
||||
import TagsKey from './TagKey';
|
||||
import TagValue from './TagValue';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -68,7 +64,6 @@ function SingleTags(props: AllTagsProps): JSX.Element {
|
||||
tag={tag}
|
||||
setLocalSelectedTags={setLocalSelectedTags}
|
||||
/>
|
||||
|
||||
<SelectComponent
|
||||
onChange={onChangeOperatorHandler}
|
||||
value={AllMenu.find((e) => e.key === selectedOperator)?.value || ''}
|
||||
@ -80,21 +75,16 @@ function SingleTags(props: AllTagsProps): JSX.Element {
|
||||
))}
|
||||
</SelectComponent>
|
||||
|
||||
<ValueSelect
|
||||
value={selectedValues}
|
||||
onChange={(value): void => {
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: value as string[],
|
||||
},
|
||||
...tags.slice(index + 1, tags.length),
|
||||
]);
|
||||
}}
|
||||
mode="tags"
|
||||
/>
|
||||
{selectedKey[0] ? (
|
||||
<TagValue
|
||||
index={index}
|
||||
tag={tag}
|
||||
setLocalSelectedTags={setLocalSelectedTags}
|
||||
tagKey={selectedKey[0]}
|
||||
/>
|
||||
) : (
|
||||
<SelectComponent />
|
||||
)}
|
||||
|
||||
<IconContainer role="button" onClick={(): void => onDeleteTagHandler(index)}>
|
||||
<CloseOutlined />
|
||||
@ -112,4 +102,10 @@ interface AllTagsProps {
|
||||
>;
|
||||
}
|
||||
|
||||
export interface Value {
|
||||
key: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export default SingleTags;
|
||||
|
@ -15,12 +15,6 @@ export const SelectComponent = styled(Select)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const ValueSelect = styled(Select)`
|
||||
&&& {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
|
@ -5,7 +5,7 @@ import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError';
|
||||
import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity';
|
||||
import { UpdateTagVisibility } from 'store/actions/trace/updateTagPanelVisiblity';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
@ -27,7 +27,7 @@ const { Paragraph } = Typography;
|
||||
function AllTags({
|
||||
updateTagIsError,
|
||||
onChangeHandler,
|
||||
updateTagVisiblity,
|
||||
updateTagVisibility,
|
||||
updateFilters,
|
||||
}: AllTagsProps): JSX.Element {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
@ -63,7 +63,7 @@ function AllTags({
|
||||
onChangeHandler(parsedQuery.payload);
|
||||
updateFilters(localSelectedTags);
|
||||
updateTagIsError(false);
|
||||
updateTagVisiblity(false);
|
||||
updateTagVisibility(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -75,7 +75,7 @@ function AllTags({
|
||||
return (
|
||||
<ErrorContainer>
|
||||
<Paragraph style={{ color: '#E89A3C' }}>
|
||||
Unrecognised query format. Please reset your query by clicking `X` in the
|
||||
Unrecognized query format. Please reset your query by clicking `X` in the
|
||||
search bar above.
|
||||
</Paragraph>
|
||||
|
||||
@ -116,14 +116,16 @@ function AllTags({
|
||||
</Wrapper>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button onClick={onResetHandler}>Reset</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onRunQueryHandler}
|
||||
icon={<CaretRightFilled />}
|
||||
>
|
||||
Run Query
|
||||
</Button>
|
||||
<Space align="start">
|
||||
<Button onClick={onResetHandler}>Reset</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onRunQueryHandler}
|
||||
icon={<CaretRightFilled />}
|
||||
>
|
||||
Run Query
|
||||
</Button>
|
||||
</Space>
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
);
|
||||
@ -131,14 +133,14 @@ function AllTags({
|
||||
|
||||
interface DispatchProps {
|
||||
updateTagIsError: (value: boolean) => void;
|
||||
updateTagVisiblity: (value: boolean) => void;
|
||||
updateTagVisibility: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch),
|
||||
updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch),
|
||||
updateTagVisibility: bindActionCreators(UpdateTagVisibility, dispatch),
|
||||
});
|
||||
|
||||
interface AllTagsProps extends DispatchProps {
|
||||
|
@ -6,7 +6,7 @@ export const Container = styled(Card)`
|
||||
min-height: 20vh;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
position: absolute !important;
|
||||
|
||||
.ant-card-body {
|
||||
padding-bottom: 0;
|
||||
@ -35,20 +35,16 @@ export const Wrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
export const ButtonContainer = styled(Card)`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
background-color: #303030;
|
||||
padding-top: 11px;
|
||||
padding-bottom: 11px;
|
||||
padding-right: 38.01px;
|
||||
|
||||
margin-top: 1rem;
|
||||
padding-top: 11px !important;
|
||||
padding-bottom: 11px !important;
|
||||
padding-right: 38.01px !important;
|
||||
|
||||
> button:nth-child(1) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
margin-top: 1rem !important;
|
||||
`;
|
||||
|
||||
export const CurrentTagsContainer = styled.div`
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { CaretRightFilled } from '@ant-design/icons';
|
||||
import { Space } from 'antd';
|
||||
import useClickOutside from 'hooks/useClickOutside';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError';
|
||||
import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity';
|
||||
import { UpdateTagVisibility } from 'store/actions/trace/updateTagPanelVisiblity';
|
||||
import { updateURL } from 'store/actions/trace/util';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
@ -18,7 +17,7 @@ import { Container, SearchComponent } from './styles';
|
||||
import { parseQueryToTags, parseTagsToQuery } from './util';
|
||||
|
||||
function Search({
|
||||
updateTagVisiblity,
|
||||
updateTagVisibility,
|
||||
updateTagIsError,
|
||||
}: SearchProps): JSX.Element {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
@ -66,7 +65,7 @@ function Search({
|
||||
!(e.ariaSelected === 'true') &&
|
||||
traces.isTagModalOpen
|
||||
) {
|
||||
updateTagVisiblity(false);
|
||||
updateTagVisibility(false);
|
||||
}
|
||||
});
|
||||
|
||||
@ -75,7 +74,7 @@ function Search({
|
||||
};
|
||||
|
||||
const setIsTagsModalHandler = (value: boolean): void => {
|
||||
updateTagVisiblity(value);
|
||||
updateTagVisibility(value);
|
||||
};
|
||||
|
||||
const onFocusHandler: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
@ -96,6 +95,7 @@ function Search({
|
||||
selectedFilter: traces.selectedFilter,
|
||||
userSelected: traces.userSelectedFilter,
|
||||
isFilterExclude: traces.isFilterExclude,
|
||||
order: traces.spansAggregate.order,
|
||||
},
|
||||
});
|
||||
|
||||
@ -104,60 +104,58 @@ function Search({
|
||||
traces.filterToFetchData,
|
||||
traces.spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
traces.filter,
|
||||
traces.isFilterExclude,
|
||||
traces.userSelectedFilter,
|
||||
traces.spansAggregate.order,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Container ref={tagRef}>
|
||||
<SearchComponent
|
||||
onChange={(event): void => onChangeHandler(event.target.value)}
|
||||
value={value}
|
||||
allowClear
|
||||
disabled={traces.filterLoading}
|
||||
onFocus={onFocusHandler}
|
||||
placeholder="Click to filter by tags"
|
||||
type="search"
|
||||
enterButton={<CaretRightFilled />}
|
||||
onSearch={(string): void => {
|
||||
if (string.length === 0) {
|
||||
updateTagVisiblity(false);
|
||||
updateFilters([]);
|
||||
return;
|
||||
}
|
||||
<Container ref={tagRef}>
|
||||
<SearchComponent
|
||||
onChange={(event): void => onChangeHandler(event.target.value)}
|
||||
value={value}
|
||||
allowClear
|
||||
disabled={traces.filterLoading}
|
||||
onFocus={onFocusHandler}
|
||||
placeholder="Click to filter by tags"
|
||||
type="search"
|
||||
enterButton={<CaretRightFilled />}
|
||||
onSearch={(string): void => {
|
||||
if (string.length === 0) {
|
||||
updateTagVisibility(false);
|
||||
updateFilters([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const { isError, payload } = parseQueryToTags(string);
|
||||
const { isError, payload } = parseQueryToTags(string);
|
||||
|
||||
if (isError) {
|
||||
updateTagIsError(true);
|
||||
} else {
|
||||
updateTagIsError(false);
|
||||
updateTagVisiblity(false);
|
||||
updateFilters(payload);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
if (isError) {
|
||||
updateTagIsError(true);
|
||||
} else {
|
||||
updateTagIsError(false);
|
||||
updateTagVisibility(false);
|
||||
updateFilters(payload);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{traces.isTagModalOpen && (
|
||||
<Tags updateFilters={updateFilters} onChangeHandler={onChangeHandler} />
|
||||
)}
|
||||
</Container>
|
||||
</Space>
|
||||
{traces.isTagModalOpen && (
|
||||
<Tags updateFilters={updateFilters} onChangeHandler={onChangeHandler} />
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateTagVisiblity: (value: boolean) => void;
|
||||
updateTagVisibility: (value: boolean) => void;
|
||||
updateTagIsError: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch),
|
||||
updateTagVisibility: bindActionCreators(UpdateTagVisibility, dispatch),
|
||||
updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch),
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@ const { Search } = Input;
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SearchComponent = styled(Search)`
|
||||
|
@ -3,35 +3,36 @@ import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
GetSpansAggregate,
|
||||
GetSpansAggregateProps,
|
||||
} from 'store/actions/trace/getInitialSpansAggregate';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { updateURL } from 'store/actions/trace/util';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import {
|
||||
UPDATE_SPAN_ORDER,
|
||||
UPDATE_SPANS_AGGREGATE_PAGE_NUMBER,
|
||||
} from 'types/actions/trace';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
function TraceTable(): JSX.Element {
|
||||
const {
|
||||
spansAggregate,
|
||||
selectedFilter,
|
||||
selectedTags,
|
||||
filterLoading,
|
||||
userSelectedFilter,
|
||||
filter,
|
||||
isFilterExclude,
|
||||
filterToFetchData,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const { loading, total } = spansAggregate;
|
||||
const { loading, total, order: spansAggregateOrder } = spansAggregate;
|
||||
|
||||
type TableType = FlatArray<TraceReducer['spansAggregate']['data'], 1>;
|
||||
|
||||
@ -39,30 +40,17 @@ 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 getValue = (value: string): JSX.Element => {
|
||||
return <Typography>{value}</Typography>;
|
||||
};
|
||||
|
||||
const getHttpMethodOrStatus = (
|
||||
value: TableType['httpMethod'],
|
||||
record: TableType,
|
||||
): JSX.Element => {
|
||||
if (value.length === 0) {
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>-</Typography>
|
||||
</Link>
|
||||
);
|
||||
return <Typography>-</Typography>;
|
||||
}
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Tag color="magenta">{value}</Tag>
|
||||
</Link>
|
||||
);
|
||||
return <Tag color="magenta">{value}</Tag>;
|
||||
};
|
||||
|
||||
const columns: ColumnsType<TableType> = [
|
||||
@ -71,13 +59,9 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
sorter: true,
|
||||
render: (value: TableType['timestamp'], record): JSX.Element => {
|
||||
render: (value: TableType['timestamp']): JSX.Element => {
|
||||
const day = dayjs(value);
|
||||
return (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>{day.format('YYYY/MM/DD HH:mm:ss')}</Typography>
|
||||
</Link>
|
||||
);
|
||||
return <Typography>{day.format('YYYY/MM/DD HH:mm:ss')}</Typography>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -96,14 +80,13 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
title: 'Duration',
|
||||
dataIndex: 'durationNano',
|
||||
key: 'durationNano',
|
||||
render: (value: TableType['durationNano'], record): JSX.Element => (
|
||||
<Link to={getLink(record)}>
|
||||
<Typography>
|
||||
{`${dayjs
|
||||
.duration({ milliseconds: value / 1000000 })
|
||||
.asMilliseconds()} ms`}
|
||||
</Typography>
|
||||
</Link>
|
||||
render: (value: TableType['durationNano']): JSX.Element => (
|
||||
<Typography>
|
||||
{`${dayjs
|
||||
.duration({ milliseconds: value / 1000000 })
|
||||
.asMilliseconds()
|
||||
.toFixed(2)} ms`}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
@ -126,17 +109,33 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
sort,
|
||||
) => {
|
||||
if (!Array.isArray(sort)) {
|
||||
const { order = 'ascend' } = sort;
|
||||
const { order = spansAggregateOrder } = 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',
|
||||
const spanOrder = order || spansAggregateOrder;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_SPAN_ORDER,
|
||||
payload: {
|
||||
order: spanOrder,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREGATE_PAGE_NUMBER,
|
||||
payload: {
|
||||
currentPage: props.current,
|
||||
},
|
||||
});
|
||||
|
||||
updateURL(
|
||||
selectedFilter,
|
||||
filterToFetchData,
|
||||
props.current,
|
||||
selectedTags,
|
||||
isFilterExclude,
|
||||
userSelectedFilter,
|
||||
spanOrder,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -147,10 +146,17 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
dataSource={spansAggregate.data}
|
||||
loading={loading || filterLoading}
|
||||
columns={columns}
|
||||
rowKey="timestamp"
|
||||
rowKey={(record): string => `${record.traceID}-${record.spanID}`}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onRow={(record): React.HTMLAttributes<TableType> => ({
|
||||
onClick: (event): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
history.push(getLink(record));
|
||||
},
|
||||
})}
|
||||
pagination={{
|
||||
current: spansAggregate.currentPage,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
@ -158,20 +164,9 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
|
||||
position: ['bottomLeft'],
|
||||
total,
|
||||
}}
|
||||
sortDirections={['ascend', 'descend']}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
getSpansAggregate: (props: GetSpansAggregateProps) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getSpansAggregate: bindActionCreators(GetSpansAggregate, dispatch),
|
||||
});
|
||||
|
||||
type TraceProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TraceTable);
|
||||
export default TraceTable;
|
||||
|
@ -1,42 +1,51 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { expect } from '@jest/globals';
|
||||
import { render } from '@testing-library/react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import TraceFlameGraph from 'container/TraceFlameGraph';
|
||||
import React, { useState } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from 'store';
|
||||
|
||||
test('loads and displays greeting', () => {
|
||||
const onSpanHover = useState('');
|
||||
const { rerender } = renderHook(() => useState(''));
|
||||
|
||||
const { asFragment } = render(
|
||||
<TraceFlameGraph
|
||||
{...{
|
||||
hoveredSpanId: '',
|
||||
intervalUnit: { multiplier: 0, name: 'm' },
|
||||
onSpanHover: onSpanHover[1],
|
||||
onSpanSelect: (): void => {},
|
||||
selectedSpanId: '',
|
||||
traceMetaData: {
|
||||
globalEnd: 0,
|
||||
globalStart: 0,
|
||||
levels: 0,
|
||||
spread: 0,
|
||||
totalSpans: 0,
|
||||
},
|
||||
treeData: {
|
||||
children: [],
|
||||
id: '',
|
||||
name: '',
|
||||
serviceColour: '',
|
||||
serviceName: '',
|
||||
startTime: 0,
|
||||
tags: [],
|
||||
time: 0,
|
||||
value: 0,
|
||||
event: [],
|
||||
hasError: false,
|
||||
parent: undefined,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
<Provider store={store}>
|
||||
<TraceFlameGraph
|
||||
{...{
|
||||
hoveredSpanId: '',
|
||||
intervalUnit: { multiplier: 0, name: 'm' },
|
||||
onSpanHover: rerender,
|
||||
onSpanSelect: (): void => {},
|
||||
selectedSpanId: '',
|
||||
traceMetaData: {
|
||||
globalEnd: 0,
|
||||
globalStart: 0,
|
||||
levels: 0,
|
||||
spread: 0,
|
||||
totalSpans: 0,
|
||||
},
|
||||
treeData: {
|
||||
children: [],
|
||||
id: '',
|
||||
name: '',
|
||||
serviceColour: '#a0e',
|
||||
serviceName: '',
|
||||
startTime: 0,
|
||||
tags: [],
|
||||
time: 100,
|
||||
value: 100,
|
||||
event: [],
|
||||
hasError: false,
|
||||
parent: undefined,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Provider>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`loads and displays greeting 1`] = `<DocumentFragment />`;
|
||||
exports[`loads and displays greeting 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="sc-gsDKAQ jFDWPs"
|
||||
height="0"
|
||||
>
|
||||
<div
|
||||
class="sc-bdvvtL fyFVjh"
|
||||
title="
|
||||
0 m"
|
||||
width="Infinity"
|
||||
/>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
110
frontend/src/container/Version/index.tsx
Normal file
110
frontend/src/container/Version/index.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { WarningFilled } from '@ant-design/icons';
|
||||
import { Button, Card, Form, Space, Typography } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { InputComponent } from './styles';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function Version(): JSX.Element {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onClickUpgradeHandler = useCallback((link: string) => {
|
||||
window.open(link, '_blank');
|
||||
}, []);
|
||||
|
||||
const {
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
isCurrentVersionError,
|
||||
isLatestVersionError,
|
||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const isLatestVersion = currentVersion === latestVersion;
|
||||
const isError = isCurrentVersionError || isLatestVersionError;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Title ellipsis level={4}>
|
||||
{t('version')}
|
||||
</Title>
|
||||
|
||||
<Form
|
||||
wrapperCol={{
|
||||
span: 14,
|
||||
}}
|
||||
labelCol={{
|
||||
span: 3,
|
||||
}}
|
||||
layout="horizontal"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Form.Item label={t('current_version')}>
|
||||
<InputComponent
|
||||
readOnly
|
||||
value={isCurrentVersionError ? t('n_a').toString() : currentVersion}
|
||||
placeholder={t('current_version')}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('latest_version')}>
|
||||
<InputComponent
|
||||
readOnly
|
||||
value={isLatestVersionError ? t('n_a').toString() : latestVersion}
|
||||
placeholder={t('latest_version')}
|
||||
/>
|
||||
<Button
|
||||
onClick={(): void =>
|
||||
onClickUpgradeHandler('https://github.com/SigNoz/signoz/releases')
|
||||
}
|
||||
type="link"
|
||||
>
|
||||
{t('release_notes')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
{!isError && isLatestVersion && (
|
||||
<div>
|
||||
<Space align="start">
|
||||
<span>✅</span>
|
||||
<Typography.Paragraph italic>
|
||||
{t('latest_version_signoz')}
|
||||
</Typography.Paragraph>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isError && !isLatestVersion && (
|
||||
<div>
|
||||
<Space align="start">
|
||||
<span>
|
||||
<WarningFilled style={{ color: '#E87040' }} />
|
||||
</span>
|
||||
<Typography.Paragraph italic>{t('stale_version')}</Typography.Paragraph>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isError && !isLatestVersion && (
|
||||
<Button
|
||||
onClick={(): void =>
|
||||
onClickUpgradeHandler(
|
||||
'https://signoz.io/docs/operate/docker-standalone/#upgrade',
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('read_how_to_upgrade')}
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default Version;
|
8
frontend/src/container/Version/styles.ts
Normal file
8
frontend/src/container/Version/styles.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Input } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const InputComponent = styled(Input)`
|
||||
&&& {
|
||||
max-width: 183px;
|
||||
}
|
||||
`;
|
@ -4,6 +4,7 @@ import './ReactI18';
|
||||
import AppRoutes from 'AppRoutes';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Provider } from 'react-redux';
|
||||
import reportWebVitals from 'reportWebVitals';
|
||||
import store from 'store';
|
||||
@ -12,12 +13,16 @@ if (process.env.NODE_ENV === 'development') {
|
||||
reportWebVitals(console.log);
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<React.StrictMode>
|
||||
<AppRoutes />
|
||||
</React.StrictMode>
|
||||
</Provider>,
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<React.StrictMode>
|
||||
<AppRoutes />
|
||||
</React.StrictMode>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
document.querySelector('#root'),
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { expect } from '@jest/globals';
|
||||
import dayjs from 'dayjs';
|
||||
import getStep, { DefaultStepSize } from 'lib/getStep';
|
||||
import getStep, { DefaultStepSize, MaxDataPoints } from 'lib/getStep';
|
||||
|
||||
describe('lib/getStep', () => {
|
||||
test('should return default step when the given range is less than 1 day', () => {
|
||||
@ -40,7 +40,8 @@ describe('lib/getStep', () => {
|
||||
const startUnix = start.valueOf();
|
||||
const endUnix = end.valueOf();
|
||||
|
||||
const expectedStepSize = end.diff(start, 'days') * DefaultStepSize;
|
||||
const expectedStepSize = Math.floor(end.diff(start, 's') / MaxDataPoints);
|
||||
|
||||
expect(
|
||||
getStep({
|
||||
start: startUnix / 1e3,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SettingPeroid } from 'container/GeneralSettings';
|
||||
import { SettingPeriod } from 'container/GeneralSettings';
|
||||
|
||||
const converIntoHr = (value: number, peroid: SettingPeroid): number => {
|
||||
const converIntoHr = (value: number, peroid: SettingPeriod): number => {
|
||||
if (peroid === 'day') {
|
||||
return value * 24;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { SettingPeroid } from 'container/GeneralSettings';
|
||||
import { SettingPeriod } from 'container/GeneralSettings';
|
||||
|
||||
const getSettingsPeroid = (hr: number): PayloadProps => {
|
||||
if (hr <= 0) {
|
||||
@ -30,7 +30,7 @@ const getSettingsPeroid = (hr: number): PayloadProps => {
|
||||
|
||||
interface PayloadProps {
|
||||
value: number;
|
||||
peroid: SettingPeroid;
|
||||
peroid: SettingPeriod;
|
||||
}
|
||||
|
||||
export default getSettingsPeroid;
|
||||
|
@ -30,6 +30,7 @@ const convertToMs = (
|
||||
};
|
||||
|
||||
export const DefaultStepSize = 60;
|
||||
export const MaxDataPoints = 200;
|
||||
|
||||
/**
|
||||
* Returns relevant step size based on given start and end date.
|
||||
@ -37,13 +38,9 @@ export const DefaultStepSize = 60;
|
||||
const getStep = ({ start, end, inputFormat = 'ms' }: GetStepInput): number => {
|
||||
const startDate = dayjs(convertToMs(Number(start), inputFormat));
|
||||
const endDate = dayjs(convertToMs(Number(end), inputFormat));
|
||||
const diffDays = Math.abs(endDate.diff(startDate, 'days'));
|
||||
const diffSec = Math.abs(endDate.diff(startDate, 's'));
|
||||
|
||||
if (diffDays > 1) {
|
||||
return DefaultStepSize * diffDays;
|
||||
}
|
||||
|
||||
return DefaultStepSize;
|
||||
return Math.max(Math.floor(diffSec / MaxDataPoints), DefaultStepSize);
|
||||
};
|
||||
|
||||
export default getStep;
|
||||
|
@ -1,12 +1,13 @@
|
||||
/* eslint-disable */
|
||||
//@ts-nocheck
|
||||
|
||||
import { Card } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { ForceGraph2D } from 'react-force-graph';
|
||||
import { connect } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { getDetailedServiceMapItems, getServiceMapItems } from 'store/actions';
|
||||
import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
@ -31,9 +32,8 @@ const Container = styled.div`
|
||||
`;
|
||||
|
||||
interface ServiceMapProps extends RouteComponentProps<any> {
|
||||
serviceMap: serviceMapStore;
|
||||
serviceMap: ServiceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
getServiceMapItems: (time: GlobalTime) => void;
|
||||
getDetailedServiceMapItems: (time: GlobalTime) => void;
|
||||
}
|
||||
interface graphNode {
|
||||
@ -53,29 +53,32 @@ export interface graphDataType {
|
||||
function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||
const fgRef = useRef();
|
||||
|
||||
const {
|
||||
getDetailedServiceMapItems,
|
||||
getServiceMapItems,
|
||||
globalTime,
|
||||
serviceMap,
|
||||
} = props;
|
||||
const { getDetailedServiceMapItems, globalTime, serviceMap } = props;
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
Call the apis only when the route is loaded.
|
||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||
*/
|
||||
getServiceMapItems(globalTime);
|
||||
getDetailedServiceMapItems(globalTime);
|
||||
}, [globalTime, getServiceMapItems, getDetailedServiceMapItems]);
|
||||
}, [globalTime, getDetailedServiceMapItems]);
|
||||
|
||||
useEffect(() => {
|
||||
fgRef.current && fgRef.current.d3Force('charge').strength(-400);
|
||||
});
|
||||
if (!serviceMap.items.length || !serviceMap.services.length) {
|
||||
|
||||
if (serviceMap.loading) {
|
||||
return <Spinner size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (!serviceMap.loading && serviceMap.items.length === 0) {
|
||||
return (
|
||||
<Container>
|
||||
<Card>No Service Found</Card>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const zoomToService = (value: string): void => {
|
||||
fgRef &&
|
||||
fgRef.current &&
|
||||
@ -149,7 +152,6 @@ const mapStateToProps = (
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, {
|
||||
getServiceMapItems,
|
||||
getDetailedServiceMapItems,
|
||||
})(ServiceMap),
|
||||
);
|
||||
|
@ -84,7 +84,7 @@ function _UsageExplorer(props: UsageExplorerProps): JSX.Element {
|
||||
if (selectedTime && selectedInterval) {
|
||||
const maxTime = new Date().getTime() * 1000000;
|
||||
const minTime = maxTime - selectedTime.value * 24 * 3600000 * 1000000;
|
||||
|
||||
|
||||
getUsageData(minTime, maxTime, selectedInterval.value, selectedService);
|
||||
}
|
||||
}, [selectedTime, selectedInterval, selectedService, getUsageData]);
|
||||
|
@ -5,29 +5,32 @@ import CreateAlertChannels from 'container/CreateAlertChannels';
|
||||
import GeneralSettings from 'container/GeneralSettings';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function SettingsPage(): JSX.Element {
|
||||
const pathName = history.location.pathname;
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RouteTab
|
||||
{...{
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: 'General Settings',
|
||||
name: t('routes.general'),
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: (): JSX.Element => {
|
||||
return <CreateAlertChannels preType="slack" />;
|
||||
},
|
||||
name: 'Alert Channels',
|
||||
name: t('routes.alert_channels'),
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey:
|
||||
pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels',
|
||||
pathName === ROUTES.SETTINGS
|
||||
? t('routes.general')
|
||||
: t('routes.alert_channels'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -4,27 +4,30 @@ import AlertChannels from 'container/AllAlertChannels';
|
||||
import GeneralSettings from 'container/GeneralSettings';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function AllAlertChannels(): JSX.Element {
|
||||
const pathName = history.location.pathname;
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RouteTab
|
||||
{...{
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: 'General Settings',
|
||||
name: t('routes.general'),
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
Component: AlertChannels,
|
||||
name: 'Alert Channels',
|
||||
name: t('routes.alert_channels'),
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey:
|
||||
pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels',
|
||||
pathName === ROUTES.SETTINGS
|
||||
? t('routes.general')
|
||||
: t('routes.alert_channels'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -14,7 +14,7 @@ function SettingsPage(): JSX.Element {
|
||||
routes: [
|
||||
{
|
||||
Component: GeneralSettings,
|
||||
name: 'General Settings',
|
||||
name: 'General',
|
||||
route: ROUTES.SETTINGS,
|
||||
},
|
||||
{
|
||||
@ -23,8 +23,7 @@ function SettingsPage(): JSX.Element {
|
||||
route: ROUTES.ALL_CHANNELS,
|
||||
},
|
||||
],
|
||||
activeKey:
|
||||
pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General Settings',
|
||||
activeKey: pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
8
frontend/src/pages/Status/index.tsx
Normal file
8
frontend/src/pages/Status/index.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import Version from 'container/Version';
|
||||
import React from 'react';
|
||||
|
||||
function Status(): JSX.Element {
|
||||
return <Version />;
|
||||
}
|
||||
|
||||
export default Status;
|
@ -5,6 +5,7 @@ import TraceGraph from 'container/Trace/Graph';
|
||||
import Search from 'container/Trace/Search';
|
||||
import TraceGraphFilter from 'container/Trace/TraceGraphFilter';
|
||||
import TraceTable from 'container/Trace/TraceTable';
|
||||
import getStep from 'lib/getStep';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
@ -63,7 +64,7 @@ function Trace({
|
||||
current: spansAggregate.currentPage,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
selectedTags,
|
||||
order: 'ascending',
|
||||
order: spansAggregate.order === 'ascend' ? 'ascending' : 'descending',
|
||||
});
|
||||
}, [
|
||||
selectedTags,
|
||||
@ -73,6 +74,7 @@ function Trace({
|
||||
getSpansAggregate,
|
||||
spansAggregate.currentPage,
|
||||
spansAggregate.pageSize,
|
||||
spansAggregate.order,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -83,7 +85,7 @@ function Trace({
|
||||
selectedFilter,
|
||||
selectedTags,
|
||||
start: minTime,
|
||||
step: 60,
|
||||
step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }),
|
||||
isFilterExclude,
|
||||
});
|
||||
}, [
|
||||
@ -93,8 +95,8 @@ function Trace({
|
||||
selectedTags,
|
||||
maxTime,
|
||||
minTime,
|
||||
isFilterExclude,
|
||||
getSpans,
|
||||
isFilterExclude,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -2,27 +2,30 @@ import { Typography } from 'antd';
|
||||
import getTraceItem from 'api/trace/getTraceItem';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TraceDetailContainer from 'container/TraceDetail';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import React from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Props as TraceDetailProps } from 'types/api/trace/getTraceItem';
|
||||
|
||||
function TraceDetail(): JSX.Element {
|
||||
const { id } = useParams<TraceDetailProps>();
|
||||
const { data: traceDetailResponse, error, isLoading, isError } = useQuery(
|
||||
`getTraceItem/${id}`,
|
||||
() => getTraceItem({ id }),
|
||||
{
|
||||
cacheTime: 3000,
|
||||
},
|
||||
);
|
||||
|
||||
const traceDetailResponse = useFetch(getTraceItem, {
|
||||
id,
|
||||
});
|
||||
|
||||
if (traceDetailResponse.error) {
|
||||
if (traceDetailResponse?.error || error || isError) {
|
||||
return (
|
||||
<Typography>
|
||||
{traceDetailResponse.errorMessage || 'Something went wrong'}
|
||||
{traceDetailResponse?.error || 'Something went wrong'}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (traceDetailResponse.loading || traceDetailResponse.payload === undefined) {
|
||||
if (isLoading || !(traceDetailResponse && traceDetailResponse.payload)) {
|
||||
return <Spinner tip="Loading.." />;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems
|
||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import GetStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getStep from 'lib/getStep';
|
||||
import { Dispatch } from 'redux';
|
||||
import store from 'store';
|
||||
import AppActions from 'types/actions';
|
||||
@ -45,7 +46,7 @@ export const GetQueryResults = (
|
||||
end,
|
||||
query: encodeURIComponent(query.query),
|
||||
start,
|
||||
step: '60',
|
||||
step: `${getStep({ start, end, inputFormat: 'ms' })}`,
|
||||
});
|
||||
return {
|
||||
query: query.query,
|
||||
|
@ -7,6 +7,7 @@ import { ActionTypes } from './types';
|
||||
export interface ServiceMapStore {
|
||||
items: ServicesMapItem[];
|
||||
services: ServicesItem[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export interface ServicesItem {
|
||||
@ -37,38 +38,39 @@ export interface ServicesAction {
|
||||
payload: ServicesItem[];
|
||||
}
|
||||
|
||||
export const getServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch): Promise<void> => {
|
||||
dispatch<ServiceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: [],
|
||||
});
|
||||
|
||||
const requestString = `/serviceMapDependencies?start=${globalTime.minTime}&end=${globalTime.maxTime}`;
|
||||
|
||||
const response = await api.get<ServicesMapItem[]>(requestString);
|
||||
|
||||
dispatch<ServiceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: response.data,
|
||||
});
|
||||
export interface ServiceMapLoading {
|
||||
type: ActionTypes.serviceMapLoading;
|
||||
payload: {
|
||||
loading: ServiceMapStore['loading'];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch): Promise<void> => {
|
||||
dispatch<ServicesAction>({
|
||||
type: ActionTypes.getServices,
|
||||
payload: [],
|
||||
});
|
||||
|
||||
const requestString = `/services?start=${globalTime.minTime}&end=${globalTime.maxTime}`;
|
||||
|
||||
const response = await api.get<ServicesItem[]>(requestString);
|
||||
const serviceMapDependencies = `/serviceMapDependencies?start=${globalTime.minTime}&end=${globalTime.maxTime}`;
|
||||
|
||||
const [serviceMapDependenciesResponse, response] = await Promise.all([
|
||||
api.get<ServicesMapItem[]>(serviceMapDependencies),
|
||||
api.get<ServicesItem[]>(requestString),
|
||||
]);
|
||||
|
||||
dispatch<ServicesAction>({
|
||||
type: ActionTypes.getServices,
|
||||
payload: response.data,
|
||||
});
|
||||
|
||||
dispatch<ServiceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: serviceMapDependenciesResponse.data,
|
||||
});
|
||||
|
||||
dispatch<ServiceMapLoading>({
|
||||
type: ActionTypes.serviceMapLoading,
|
||||
payload: {
|
||||
loading: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
parseIsSkippedSelection,
|
||||
parseQueryIntoCurrent,
|
||||
parseQueryIntoFilter,
|
||||
parseQueryIntoOrder,
|
||||
parseQueryIntoSelectedTags,
|
||||
parseSelectedFilter,
|
||||
} from './util';
|
||||
@ -66,6 +67,11 @@ export const GetInitialTraceFilter = (
|
||||
traces.spansAggregate.currentPage,
|
||||
);
|
||||
|
||||
const parsedQueryOrder = parseQueryIntoOrder(
|
||||
query,
|
||||
traces.spansAggregate.order,
|
||||
);
|
||||
|
||||
const isSelectionSkipped = parseIsSkippedSelection(query);
|
||||
|
||||
const parsedSelectedTags = parseQueryIntoSelectedTags(
|
||||
@ -148,6 +154,7 @@ export const GetInitialTraceFilter = (
|
||||
selectedTags: parsedSelectedTags.currentValue,
|
||||
userSelected: getUserSelected.currentValue,
|
||||
isFilterExclude: getIsFilterExcluded.currentValue,
|
||||
order: parsedQueryOrder.currentValue,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
@ -3,11 +3,13 @@ import getSpansAggregate from 'api/trace/getSpansAggregate';
|
||||
import { Dispatch, Store } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_SPANS_AGGREEGATE } from 'types/actions/trace';
|
||||
import { UPDATE_SPANS_AGGREGATE } from 'types/actions/trace';
|
||||
import { Props as GetSpanAggregateProps } from 'types/api/trace/getSpanAggregate';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { updateURL } from './util';
|
||||
|
||||
export const GetSpansAggregate = (
|
||||
props: GetSpansAggregateProps,
|
||||
): ((
|
||||
@ -29,10 +31,12 @@ export const GetSpansAggregate = (
|
||||
return;
|
||||
}
|
||||
|
||||
const order = props.order === 'ascending' ? 'ascend' : 'descend';
|
||||
|
||||
try {
|
||||
// triggering loading
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
type: UPDATE_SPANS_AGGREGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
@ -41,6 +45,7 @@ export const GetSpansAggregate = (
|
||||
error: false,
|
||||
total: spansAggregate.total,
|
||||
pageSize: props.pageSize,
|
||||
order,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -53,12 +58,12 @@ export const GetSpansAggregate = (
|
||||
offset: props.current * props.pageSize - props.pageSize,
|
||||
selectedTags: props.selectedTags,
|
||||
isFilterExclude: traces.isFilterExclude,
|
||||
order: props.order,
|
||||
order,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
type: UPDATE_SPANS_AGGREGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
@ -67,16 +72,27 @@ export const GetSpansAggregate = (
|
||||
error: false,
|
||||
total: response.payload.totalSpans,
|
||||
pageSize: props.pageSize,
|
||||
order,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
updateURL(
|
||||
traces.selectedFilter,
|
||||
traces.filterToFetchData,
|
||||
props.current,
|
||||
traces.selectedTags,
|
||||
traces.isFilterExclude,
|
||||
traces.userSelectedFilter,
|
||||
order,
|
||||
);
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
type: UPDATE_SPANS_AGGREGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
@ -85,13 +101,14 @@ export const GetSpansAggregate = (
|
||||
error: true,
|
||||
total: spansAggregate.total,
|
||||
pageSize: props.pageSize,
|
||||
order,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
type: UPDATE_SPANS_AGGREGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
@ -100,6 +117,7 @@ export const GetSpansAggregate = (
|
||||
error: true,
|
||||
total: spansAggregate.total,
|
||||
pageSize: props.pageSize,
|
||||
order,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,4 +1,3 @@
|
||||
export * from './current';
|
||||
export * from './filter';
|
||||
export * from './filterToFetchData';
|
||||
export * from './isFilterExclude';
|
||||
@ -6,3 +5,5 @@ export * from './minMaxTime';
|
||||
export * from './selectedFilter';
|
||||
export * from './selectedTags';
|
||||
export * from './skippedSelected';
|
||||
export * from './spanAggregateCurrentPage';
|
||||
export * from './spanAggregateOrder';
|
||||
|
@ -10,7 +10,7 @@ export const parseQueryIntoCurrent = (
|
||||
|
||||
let current = 1;
|
||||
|
||||
const selected = url.get('current');
|
||||
const selected = url.get('spanAggregateCurrentPage');
|
||||
|
||||
if (selected) {
|
||||
try {
|
@ -0,0 +1,39 @@
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { ParsedUrl } from '../util';
|
||||
|
||||
export const parseQueryIntoOrder = (
|
||||
query: string,
|
||||
stateCurrent: TraceReducer['spansAggregate']['order'],
|
||||
): ParsedUrl<TraceReducer['spansAggregate']['order']> => {
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
let current = 'ascend';
|
||||
|
||||
const selected = url.get('spanAggregateOrder');
|
||||
|
||||
if (selected) {
|
||||
try {
|
||||
const parsedValue = selected;
|
||||
|
||||
if (parsedValue && typeof parsedValue === 'string') {
|
||||
current = parsedValue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log('error while parsing json');
|
||||
}
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
return {
|
||||
currentValue: current,
|
||||
urlValue: current,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue: stateCurrent,
|
||||
urlValue: current,
|
||||
};
|
||||
};
|
@ -44,9 +44,9 @@ export const SelectedTraceFilter = (props: {
|
||||
traces.filterToFetchData,
|
||||
traces.spansAggregate.currentPage,
|
||||
traces.selectedTags,
|
||||
traces.filter,
|
||||
traces.isFilterExclude,
|
||||
traces.userSelectedFilter,
|
||||
traces.spansAggregate.order,
|
||||
);
|
||||
};
|
||||
};
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_TAG_MODAL_VISIBLITY } from 'types/actions/trace';
|
||||
import { UPDATE_TAG_MODAL_VISIBILITY } from 'types/actions/trace';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateTagVisiblity = (
|
||||
export const UpdateTagVisibility = (
|
||||
isTagModalOpen: TraceReducer['isTagModalOpen'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch): void => {
|
||||
dispatch({
|
||||
type: UPDATE_TAG_MODAL_VISIBLITY,
|
||||
type: UPDATE_TAG_MODAL_VISIBILITY,
|
||||
payload: {
|
||||
isTagModalOpen,
|
||||
},
|
||||
|
@ -18,11 +18,11 @@ export function isTraceFilterEnum(
|
||||
export const updateURL = (
|
||||
selectedFilter: TraceReducer['selectedFilter'],
|
||||
filterToFetchData: TraceReducer['filterToFetchData'],
|
||||
current: TraceReducer['spansAggregate']['total'],
|
||||
spanAggregateCurrentPage: TraceReducer['spansAggregate']['currentPage'],
|
||||
selectedTags: TraceReducer['selectedTags'],
|
||||
filter: TraceReducer['filter'],
|
||||
isFilterExclude: TraceReducer['isFilterExclude'],
|
||||
userSelectedFilter: TraceReducer['userSelectedFilter'],
|
||||
spanAggregateOrder: TraceReducer['spansAggregate']['order'],
|
||||
): void => {
|
||||
const search = new URLSearchParams(window.location.search);
|
||||
const preResult: { key: string; value: string }[] = [];
|
||||
@ -30,11 +30,12 @@ export const updateURL = (
|
||||
const keyToSkip = [
|
||||
'selected',
|
||||
'filterToFetchData',
|
||||
'current',
|
||||
'selectedTags',
|
||||
'filter',
|
||||
'isFilterExclude',
|
||||
'userSelectedFilter',
|
||||
'spanAggregateCurrentPage',
|
||||
'spanAggregateOrder',
|
||||
];
|
||||
|
||||
search.forEach((value, key) => {
|
||||
@ -51,15 +52,15 @@ export const updateURL = (
|
||||
Object.fromEntries(selectedFilter),
|
||||
)}&filterToFetchData=${JSON.stringify(
|
||||
filterToFetchData,
|
||||
)}¤t=${current}&selectedTags=${JSON.stringify(
|
||||
)}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&selectedTags=${JSON.stringify(
|
||||
selectedTags,
|
||||
)}&filter=${JSON.stringify(Object.fromEntries(filter))}&${preResult
|
||||
)}&${preResult
|
||||
.map((e) => `${e.key}=${e.value}`)
|
||||
.join('&')}&isFilterExclude=${JSON.stringify(
|
||||
Object.fromEntries(isFilterExclude),
|
||||
)}&userSelectedFilter=${JSON.stringify(
|
||||
Object.fromEntries(userSelectedFilter),
|
||||
)}`,
|
||||
)}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&spanAggregateOrder=${spanAggregateOrder}`,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,14 +1,22 @@
|
||||
import { ServiceMapItemAction, ServicesAction } from './serviceMap';
|
||||
import {
|
||||
ServiceMapItemAction,
|
||||
ServiceMapLoading,
|
||||
ServicesAction,
|
||||
} from './serviceMap';
|
||||
import { GetUsageDataAction } from './usage';
|
||||
|
||||
export enum ActionTypes {
|
||||
updateTraceFilters = 'UPDATE_TRACES_FILTER',
|
||||
updateTimeInterval = 'UPDATE_TIME_INTERVAL',
|
||||
getServiceMapItems = 'GET_SERVICE_MAP_ITEMS',
|
||||
getServices = 'GET_SERVICES',
|
||||
getUsageData = 'GET_USAGE_DATE',
|
||||
fetchTraces = 'FETCH_TRACES',
|
||||
fetchTraceItem = 'FETCH_TRACE_ITEM',
|
||||
serviceMapLoading = 'UPDATE_SERVICE_MAP_LOADING',
|
||||
}
|
||||
|
||||
export type Action = GetUsageDataAction | ServicesAction | ServiceMapItemAction;
|
||||
export type Action =
|
||||
| GetUsageDataAction
|
||||
| ServicesAction
|
||||
| ServiceMapItemAction
|
||||
| ServiceMapLoading;
|
||||
|
@ -7,6 +7,10 @@ import {
|
||||
LOGGED_IN,
|
||||
SIDEBAR_COLLAPSE,
|
||||
SWITCH_DARK_MODE,
|
||||
UPDATE_CURRENT_ERROR,
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_LATEST_VERSION,
|
||||
UPDATE_LATEST_VERSION_ERROR,
|
||||
} from 'types/actions/app';
|
||||
import InitialValueTypes from 'types/reducer/app';
|
||||
|
||||
@ -14,6 +18,10 @@ const InitialValue: InitialValueTypes = {
|
||||
isDarkMode: getTheme() === 'darkMode',
|
||||
isLoggedIn: getLocalStorageKey(IS_LOGGED_IN) === 'yes',
|
||||
isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||
currentVersion: '',
|
||||
latestVersion: '',
|
||||
isCurrentVersionError: false,
|
||||
isLatestVersionError: false,
|
||||
};
|
||||
|
||||
const appReducer = (
|
||||
@ -42,6 +50,28 @@ const appReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_CURRENT_VERSION: {
|
||||
return {
|
||||
...state,
|
||||
currentVersion: action.payload.currentVersion,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_LATEST_VERSION: {
|
||||
return { ...state, latestVersion: action.payload.latestVersion };
|
||||
}
|
||||
|
||||
case UPDATE_CURRENT_ERROR: {
|
||||
return { ...state, isCurrentVersionError: true };
|
||||
}
|
||||
|
||||
case UPDATE_LATEST_VERSION_ERROR: {
|
||||
return {
|
||||
...state,
|
||||
isLatestVersionError: true,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { Action, ActionTypes, ServiceMapStore } from 'store/actions';
|
||||
const initialState: ServiceMapStore = {
|
||||
items: [],
|
||||
services: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
export const ServiceMapReducer = (
|
||||
@ -20,6 +21,12 @@ export const ServiceMapReducer = (
|
||||
...state,
|
||||
services: action.payload,
|
||||
};
|
||||
case ActionTypes.serviceMapLoading: {
|
||||
return {
|
||||
...state,
|
||||
loading: action.payload.loading,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -9,8 +9,10 @@ import {
|
||||
UPDATE_SELECTED_FUNCTION,
|
||||
UPDATE_SELECTED_GROUP_BY,
|
||||
UPDATE_SELECTED_TAGS,
|
||||
UPDATE_SPANS_AGGREEGATE,
|
||||
UPDATE_TAG_MODAL_VISIBLITY,
|
||||
UPDATE_SPAN_ORDER,
|
||||
UPDATE_SPANS_AGGREGATE,
|
||||
UPDATE_SPANS_AGGREGATE_PAGE_NUMBER,
|
||||
UPDATE_TAG_MODAL_VISIBILITY,
|
||||
UPDATE_TRACE_FILTER,
|
||||
UPDATE_TRACE_FILTER_LOADING,
|
||||
UPDATE_TRACE_GRAPH_ERROR,
|
||||
@ -37,6 +39,7 @@ const initialValue: TraceReducer = {
|
||||
error: false,
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
order: 'ascend',
|
||||
},
|
||||
selectedGroupBy: '',
|
||||
selectedFunction: 'count',
|
||||
@ -71,6 +74,7 @@ const traceReducer = (
|
||||
selectedTags,
|
||||
userSelected,
|
||||
isFilterExclude,
|
||||
order,
|
||||
} = payload;
|
||||
|
||||
return {
|
||||
@ -84,6 +88,7 @@ const traceReducer = (
|
||||
spansAggregate: {
|
||||
...state.spansAggregate,
|
||||
currentPage: current,
|
||||
order,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -115,14 +120,14 @@ const traceReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SPANS_AGGREEGATE: {
|
||||
case UPDATE_SPANS_AGGREGATE: {
|
||||
return {
|
||||
...state,
|
||||
spansAggregate: action.payload.spansAggregate,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TAG_MODAL_VISIBLITY: {
|
||||
case UPDATE_TAG_MODAL_VISIBILITY: {
|
||||
return {
|
||||
...state,
|
||||
isTagModalOpen: action.payload.isTagModalOpen,
|
||||
@ -199,6 +204,26 @@ const traceReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SPAN_ORDER: {
|
||||
return {
|
||||
...state,
|
||||
spansAggregate: {
|
||||
...state.spansAggregate,
|
||||
order: action.payload.order,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SPANS_AGGREGATE_PAGE_NUMBER: {
|
||||
return {
|
||||
...state,
|
||||
spansAggregate: {
|
||||
...state.spansAggregate,
|
||||
currentPage: action.payload.currentPage,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -1,7 +1,15 @@
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
export const SWITCH_DARK_MODE = 'SWITCH_DARK_MODE';
|
||||
export const LOGGED_IN = 'LOGGED_IN';
|
||||
export const SIDEBAR_COLLAPSE = 'SIDEBAR_COLLAPSE';
|
||||
|
||||
export const UPDATE_CURRENT_VERSION = 'UPDATE_CURRENT_VERSION';
|
||||
export const UPDATE_LATEST_VERSION = 'UPDATE_LATEST_VERSION';
|
||||
|
||||
export const UPDATE_CURRENT_ERROR = 'UPDATE_CURRENT_ERROR';
|
||||
export const UPDATE_LATEST_VERSION_ERROR = 'UPDATE_LATEST_VERSION_ERROR';
|
||||
|
||||
export interface SwitchDarkMode {
|
||||
type: typeof SWITCH_DARK_MODE;
|
||||
}
|
||||
@ -15,4 +23,31 @@ export interface SideBarCollapse {
|
||||
payload: boolean;
|
||||
}
|
||||
|
||||
export type AppAction = SwitchDarkMode | LoggedInUser | SideBarCollapse;
|
||||
export interface UpdateAppVersion {
|
||||
type: typeof UPDATE_CURRENT_VERSION;
|
||||
payload: {
|
||||
currentVersion: AppReducer['currentVersion'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateLatestVersion {
|
||||
type: typeof UPDATE_LATEST_VERSION;
|
||||
payload: {
|
||||
latestVersion: AppReducer['latestVersion'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateVersionError {
|
||||
type: typeof UPDATE_CURRENT_ERROR | typeof UPDATE_LATEST_VERSION_ERROR;
|
||||
payload: {
|
||||
isError: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type AppAction =
|
||||
| SwitchDarkMode
|
||||
| LoggedInUser
|
||||
| SideBarCollapse
|
||||
| UpdateAppVersion
|
||||
| UpdateLatestVersion
|
||||
| UpdateVersionError;
|
||||
|
@ -7,9 +7,9 @@ export const UPDATE_TRACE_FILTER_LOADING = 'UPDATE_TRACE_FILTER_LOADING';
|
||||
export const SELECT_TRACE_FILTER = 'SELECT_TRACE_FILTER';
|
||||
export const UPDATE_ALL_FILTERS = 'UPDATE_ALL_FILTERS';
|
||||
export const UPDATE_SELECTED_TAGS = 'UPDATE_SELECTED_TAGS';
|
||||
export const UPDATE_TAG_MODAL_VISIBLITY = 'UPDATE_TAG_MODAL_VISIBLITY';
|
||||
export const UPDATE_TAG_MODAL_VISIBILITY = 'UPDATE_TAG_MODAL_VISIBILITY';
|
||||
|
||||
export const UPDATE_SPANS_AGGREEGATE = 'UPDATE_SPANS_AGGREEGATE';
|
||||
export const UPDATE_SPANS_AGGREGATE = 'UPDATE_SPANS_AGGREGATE';
|
||||
|
||||
export const UPDATE_IS_TAG_ERROR = 'UPDATE_IS_TAG_ERROR';
|
||||
|
||||
@ -25,6 +25,10 @@ export const UPDATE_FILTER_RESPONSE_SELECTED =
|
||||
'UPDATE_FILTER_RESPONSE_SELECTED';
|
||||
export const UPDATE_FILTER_EXCLUDE = 'UPDATE_FILTER_EXCLUDE';
|
||||
|
||||
export const UPDATE_SPAN_ORDER = 'UPDATE_SPAN_ORDER';
|
||||
export const UPDATE_SPANS_AGGREGATE_PAGE_NUMBER =
|
||||
'UPDATE_SPANS_AGGREGATE_PAGE_NUMBER';
|
||||
|
||||
export interface UpdateFilter {
|
||||
type: typeof UPDATE_TRACE_FILTER;
|
||||
payload: {
|
||||
@ -33,14 +37,14 @@ export interface UpdateFilter {
|
||||
}
|
||||
|
||||
export interface UpdateSpansAggregate {
|
||||
type: typeof UPDATE_SPANS_AGGREEGATE;
|
||||
type: typeof UPDATE_SPANS_AGGREGATE;
|
||||
payload: {
|
||||
spansAggregate: TraceReducer['spansAggregate'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateTagVisiblity {
|
||||
type: typeof UPDATE_TAG_MODAL_VISIBLITY;
|
||||
export interface UpdateTagVisibility {
|
||||
type: typeof UPDATE_TAG_MODAL_VISIBILITY;
|
||||
payload: {
|
||||
isTagModalOpen: TraceReducer['isTagModalOpen'];
|
||||
};
|
||||
@ -70,6 +74,7 @@ export interface UpdateAllFilters {
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
userSelected: TraceReducer['userSelectedFilter'];
|
||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||
order: TraceReducer['spansAggregate']['order'];
|
||||
};
|
||||
}
|
||||
|
||||
@ -149,6 +154,20 @@ export interface UpdateSpans {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateSpansAggregatePageNumber {
|
||||
type: typeof UPDATE_SPANS_AGGREGATE_PAGE_NUMBER;
|
||||
payload: {
|
||||
currentPage: TraceReducer['spansAggregate']['currentPage'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateSpanOrder {
|
||||
type: typeof UPDATE_SPAN_ORDER;
|
||||
payload: {
|
||||
order: TraceReducer['spansAggregate']['order'];
|
||||
};
|
||||
}
|
||||
|
||||
export type TraceActions =
|
||||
| UpdateFilter
|
||||
| GetTraceFilter
|
||||
@ -156,7 +175,7 @@ export type TraceActions =
|
||||
| SelectTraceFilter
|
||||
| UpdateAllFilters
|
||||
| UpdateSelectedTags
|
||||
| UpdateTagVisiblity
|
||||
| UpdateTagVisibility
|
||||
| UpdateSpansAggregate
|
||||
| UpdateIsTagError
|
||||
| UpdateSelectedGroupBy
|
||||
@ -166,4 +185,6 @@ export type TraceActions =
|
||||
| UpdateSpans
|
||||
| ResetTraceFilter
|
||||
| UpdateSelected
|
||||
| UpdateFilterExclude;
|
||||
| UpdateFilterExclude
|
||||
| UpdateSpanOrder
|
||||
| UpdateSpansAggregatePageNumber;
|
||||
|
5
frontend/src/types/api/disks/getDisks.ts
Normal file
5
frontend/src/types/api/disks/getDisks.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type PayloadProps = IDiskType[];
|
||||
export interface IDiskType {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
export interface PayloadProps {
|
||||
metrics_ttl_duration_hrs: number;
|
||||
metrics_move_ttl_duration_hrs?: number;
|
||||
traces_ttl_duration_hrs: number;
|
||||
traces_move_ttl_duration_hrs?: number;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
export interface Props {
|
||||
type: 'metrics' | 'traces';
|
||||
duration: string;
|
||||
totalDuration: string;
|
||||
coldStorage?: 's3' | null;
|
||||
toColdDuration?: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
|
@ -7,7 +7,7 @@ export interface Props {
|
||||
limit: number;
|
||||
offset: number;
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
order?: 'descending' | 'ascending';
|
||||
order?: TraceReducer['spansAggregate']['order'];
|
||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||
}
|
||||
|
||||
|
13
frontend/src/types/api/trace/getTagValue.ts
Normal file
13
frontend/src/types/api/trace/getTagValue.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
export interface Props {
|
||||
start: GlobalReducer['minTime'];
|
||||
end: GlobalReducer['maxTime'];
|
||||
tagKey: string;
|
||||
}
|
||||
|
||||
interface Value {
|
||||
tagValues: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = Value[];
|
18
frontend/src/types/api/user/getLatestVersion.ts
Normal file
18
frontend/src/types/api/user/getLatestVersion.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface PayloadProps {
|
||||
body: string;
|
||||
created_at: string;
|
||||
draft: boolean;
|
||||
html_url: string;
|
||||
id: number;
|
||||
mentions_count: number;
|
||||
name: string;
|
||||
node_id: number;
|
||||
prerelease: boolean;
|
||||
published_at: string;
|
||||
tag_name: number;
|
||||
tarball_url: string;
|
||||
target_commitish: string;
|
||||
upload_url: string;
|
||||
url: string;
|
||||
zipball_url: string;
|
||||
}
|
@ -2,4 +2,8 @@ export default interface AppReducer {
|
||||
isDarkMode: boolean;
|
||||
isLoggedIn: boolean;
|
||||
isSideBarCollapsed: boolean;
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
isCurrentVersionError: boolean;
|
||||
isLatestVersionError: boolean;
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ export interface TraceReducer {
|
||||
filter: Map<TraceFilterEnum, Record<string, string>>;
|
||||
filterToFetchData: TraceFilterEnum[];
|
||||
filterLoading: boolean;
|
||||
|
||||
selectedFilter: Map<TraceFilterEnum, string[]>;
|
||||
userSelectedFilter: Map<TraceFilterEnum, string[]>;
|
||||
|
||||
isFilterExclude: Map<TraceFilterEnum, boolean>;
|
||||
selectedTags: Tags[];
|
||||
isTagModalOpen: boolean;
|
||||
@ -18,6 +20,7 @@ export interface TraceReducer {
|
||||
error: boolean;
|
||||
total: number;
|
||||
pageSize: number;
|
||||
order: string;
|
||||
};
|
||||
selectedGroupBy: string;
|
||||
selectedFunction: string;
|
||||
|
@ -25,8 +25,7 @@ export const getSpanTreeMetadata = (
|
||||
globalEnd = Math.max(globalEnd, endTime);
|
||||
if (treeNode.hasError) {
|
||||
treeNode.serviceColour = errorColor;
|
||||
}
|
||||
treeNode.serviceColour = spanServiceColours[treeNode.serviceName];
|
||||
} else treeNode.serviceColour = spanServiceColours[treeNode.serviceName];
|
||||
treeNode.children.forEach((childNode) => {
|
||||
traverse(childNode, level + 1);
|
||||
});
|
||||
|
@ -22,5 +22,5 @@
|
||||
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["./src"]
|
||||
"include": ["./src", "./babel.config.js", "./jest.config.ts"]
|
||||
}
|
||||
|
1188
frontend/yarn.lock
1188
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
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