mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 06:49:00 +08:00
fix: merge conflit resolved
This commit is contained in:
commit
cbaf9b009c
@ -106,32 +106,70 @@ Need to update [https://github.com/SigNoz/charts](https://github.com/SigNoz/char
|
||||
- [k3d](https://k3d.io/#installation)
|
||||
- [minikube](https://minikube.sigs.k8s.io/docs/start/)
|
||||
- create a k8s cluster and make sure `kubectl` points to the locally created k8s cluster
|
||||
- run `helm install -n platform --create-namespace my-release charts/signoz` to install SigNoz chart
|
||||
- run `kubectl -n platform port-forward svc/my-release-frontend 3301:3301` to make SigNoz UI available at [localhost:3301](http://localhost:3301)
|
||||
- run `make dev-install` to install SigNoz chart with `my-release` release name in `platform` namespace.
|
||||
- run `kubectl -n platform port-forward svc/my-release-signoz-frontend 3301:3301` to make SigNoz UI available at [localhost:3301](http://localhost:3301)
|
||||
|
||||
**To install HotROD sample app:**
|
||||
|
||||
```bash
|
||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-install.sh \
|
||||
| HELM_RELEASE=my-release SIGNOZ_NAMESPACE=platform bash
|
||||
```
|
||||
|
||||
**To load data with HotROD sample app:**
|
||||
|
||||
```sh
|
||||
kubectl create ns sample-application
|
||||
|
||||
kubectl -n sample-application apply -f https://raw.githubusercontent.com/SigNoz/signoz/main/sample-apps/hotrod/hotrod.yaml
|
||||
|
||||
```bash
|
||||
kubectl -n sample-application run strzal --image=djbingham/curl \
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
|
||||
'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
|
||||
'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
|
||||
```
|
||||
|
||||
**To stop the load generation:**
|
||||
|
||||
```sh
|
||||
```bash
|
||||
kubectl -n sample-application run strzal --image=djbingham/curl \
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl \
|
||||
http://locust-master:8089/stop
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl \
|
||||
http://locust-master:8089/stop
|
||||
```
|
||||
|
||||
**To delete HotROD sample app:**
|
||||
|
||||
```bash
|
||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-delete.sh \
|
||||
| HOTROD_NAMESPACE=sample-application bash
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## General Instructions
|
||||
|
||||
**Before making any significant changes, please open an issue**. Each issue
|
||||
should describe the following:
|
||||
|
||||
* Requirement - what kind of use case are you trying to solve?
|
||||
* Proposal - what do you suggest to solve the problem or improve the existing
|
||||
situation?
|
||||
* Any open questions to address
|
||||
|
||||
Discussing your proposed changes ahead of time will make the contribution
|
||||
process smooth for everyone. Once the approach is agreed upon, make your changes
|
||||
and open a pull request(s). Unless your change is small, Please consider submitting different PRs:
|
||||
|
||||
* First PR should include the overall structure of the new component:
|
||||
* Readme, configuration, interfaces or base classes etc...
|
||||
* This PR is usually trivial to review, so the size limit does not apply to
|
||||
it.
|
||||
* Second PR should include the concrete implementation of the component. If the
|
||||
size of this PR is larger than the recommended size consider splitting it in
|
||||
multiple PRs.
|
||||
* If there are multiple sub-component then ideally each one should be implemented as
|
||||
a separate pull request.
|
||||
* Last PR should include changes to any user facing documentation. And should include
|
||||
end to end tests if applicable. The component must be enabled
|
||||
only after sufficient testing, and there is enough confidence in the
|
||||
stability and quality of the component.
|
||||
|
||||
|
||||
You can always reach out to `ankit@signoz.io` to understand more about the repo and product. We are very responsive over email and [slack](https://signoz.io/slack).
|
||||
|
||||
- If you find any bugs, please create an issue
|
||||
|
@ -39,8 +39,9 @@ services:
|
||||
query-service:
|
||||
image: signoz/query-service:0.8.0
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
ports:
|
||||
- "8080:8080"
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
volumes:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
@ -85,7 +86,7 @@ services:
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "13133:13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
|
@ -12,13 +12,18 @@ server {
|
||||
gzip_http_version 1.1;
|
||||
|
||||
location / {
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
|
||||
add_header Last-Modified $date_gmt;
|
||||
if ( $uri = '/index.html' ) {
|
||||
add_header Cache-Control no-store always;
|
||||
}
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://query-service:8080/api;
|
||||
}
|
||||
|
@ -40,6 +40,9 @@ services:
|
||||
image: signoz/query-service:0.8.0
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
volumes:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
@ -82,7 +85,7 @@ services:
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "13133:13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
|
@ -39,6 +39,9 @@ services:
|
||||
image: signoz/query-service:0.8.0
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
volumes:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
@ -80,7 +83,7 @@ services:
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "13133:13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
|
@ -12,14 +12,15 @@ server {
|
||||
gzip_http_version 1.1;
|
||||
|
||||
location / {
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
|
||||
add_header Last-Modified $date_gmt;
|
||||
if ( $uri = '/index.html' ) {
|
||||
add_header Cache-Control no-store always;
|
||||
}
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager{
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
}
|
||||
|
||||
|
4
frontend/.husky/commit-msg
Executable file
4
frontend/.husky/commit-msg
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && npm run commitlint
|
@ -1 +1 @@
|
||||
12.13.0
|
||||
16.15.0
|
@ -12,6 +12,9 @@ WORKDIR /frontend
|
||||
# copy the package.json to install dependencies
|
||||
COPY package.json ./
|
||||
|
||||
# configure node_env as production
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Install the dependencies and make the folder
|
||||
RUN yarn install
|
||||
|
||||
|
1
frontend/commitlint.config.js
Normal file
1
frontend/commitlint.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
@ -13,12 +13,13 @@
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
"postinstall": "yarn husky:configure",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky",
|
||||
"playwright": "playwright test --config=./playwright.config.ts",
|
||||
"playwright:local:debug": "PWDEBUG=console yarn playwright --headed --browser=chromium"
|
||||
"playwright:local:debug": "PWDEBUG=console yarn playwright --headed --browser=chromium",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
|
||||
"commitlint": "commitlint --edit $1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
"node": ">=16.15.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
@ -108,6 +109,8 @@
|
||||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@commitlint/cli": "^16.2.4",
|
||||
"@commitlint/config-conventional": "^16.2.4",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@playwright/test": "^1.22.0",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
@ -135,7 +138,7 @@
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"compression-webpack-plugin": "^9.0.0",
|
||||
"compression-webpack-plugin": "9.0.0",
|
||||
"copy-webpack-plugin": "^8.1.0",
|
||||
"critters-webpack-plugin": "^3.0.1",
|
||||
"eslint": "^7.30.0",
|
||||
|
@ -2,5 +2,8 @@
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings"
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls"
|
||||
}
|
||||
|
@ -2,5 +2,8 @@
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings"
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls"
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { notification } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import loginApi from 'api/user/login';
|
||||
import { Logout } from 'api/utils';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -103,7 +104,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
history.push(ROUTES.UN_AUTHORIZED);
|
||||
}
|
||||
} else {
|
||||
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
||||
Logout();
|
||||
|
||||
notification.error({
|
||||
message: response.error || t('something_went_wrong'),
|
||||
|
@ -5,6 +5,7 @@ import history from 'lib/history';
|
||||
import store from 'store';
|
||||
import {
|
||||
LOGGED_IN,
|
||||
UPDATE_ORG,
|
||||
UPDATE_USER,
|
||||
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
|
||||
UPDATE_USER_ORG_ROLE,
|
||||
@ -51,5 +52,12 @@ export const Logout = (): void => {
|
||||
},
|
||||
});
|
||||
|
||||
store.dispatch({
|
||||
type: UPDATE_ORG,
|
||||
payload: {
|
||||
org: [],
|
||||
},
|
||||
});
|
||||
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
@ -27,12 +27,19 @@ function RouteTab({
|
||||
onChange={onChange}
|
||||
destroyInactiveTabPane
|
||||
activeKey={activeKey}
|
||||
animated
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...rest}
|
||||
>
|
||||
{routes.map(
|
||||
({ Component, name }): JSX.Element => (
|
||||
<TabPane tab={name} key={name}>
|
||||
({ Component, name, route }): JSX.Element => (
|
||||
<TabPane
|
||||
tabKey={route}
|
||||
animated
|
||||
destroyInactiveTabPane
|
||||
tab={name}
|
||||
key={name}
|
||||
>
|
||||
<Component />
|
||||
</TabPane>
|
||||
),
|
||||
|
@ -3,4 +3,5 @@ export const ENVIRONMENT = {
|
||||
process?.env?.FRONTEND_API_ENDPOINT ||
|
||||
process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') ||
|
||||
'',
|
||||
NODE_ENV: process?.env?.NODE_ENV,
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ const ROUTES = {
|
||||
ALL_CHANNELS: '/settings/channels',
|
||||
CHANNELS_NEW: '/setting/channels/new',
|
||||
CHANNELS_EDIT: '/setting/channels/edit/:id',
|
||||
ALL_ERROR: '/errors',
|
||||
ALL_ERROR: '/exceptions',
|
||||
ERROR_DETAIL: '/error-detail',
|
||||
VERSION: '/status',
|
||||
MY_SETTINGS: '/my-settings',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { notification, Table, Typography } from 'antd';
|
||||
import { notification, Table, Tooltip, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import getAll from 'api/errors/getAll';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -47,11 +47,13 @@ function AllErrors(): JSX.Element {
|
||||
dataIndex: 'exceptionType',
|
||||
key: 'exceptionType',
|
||||
render: (value, record): JSX.Element => (
|
||||
<Link
|
||||
to={`${ROUTES.ERROR_DETAIL}?serviceName=${record.serviceName}&errorType=${record.exceptionType}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
<Tooltip overlay={(): JSX.Element => value}>
|
||||
<Link
|
||||
to={`${ROUTES.ERROR_DETAIL}?serviceName=${record.serviceName}&errorType=${record.exceptionType}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
),
|
||||
sorter: (a, b): number =>
|
||||
a.exceptionType.charCodeAt(0) - b.exceptionType.charCodeAt(0),
|
||||
@ -61,13 +63,15 @@ function AllErrors(): JSX.Element {
|
||||
dataIndex: 'exceptionMessage',
|
||||
key: 'exceptionMessage',
|
||||
render: (value): JSX.Element => (
|
||||
<Typography.Paragraph
|
||||
ellipsis={{
|
||||
rows: 2,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Typography.Paragraph>
|
||||
<Tooltip overlay={(): JSX.Element => value}>
|
||||
<Typography.Paragraph
|
||||
ellipsis={{
|
||||
rows: 2,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Typography.Paragraph>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import getQueryResult from 'api/widgets/getQuery';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ChartData } from 'chart.js';
|
||||
import { GraphOnClickHandler } from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
@ -17,7 +15,8 @@ import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getStep from 'lib/getStep';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
@ -37,13 +36,6 @@ function FullView({
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const [state, setState] = useState<FullViewState>({
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
payload: undefined,
|
||||
});
|
||||
|
||||
const getSelectedTime = useCallback(
|
||||
() =>
|
||||
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
|
||||
@ -55,107 +47,82 @@ function FullView({
|
||||
enum: widget?.timePreferance || 'GLOBAL_TIME',
|
||||
});
|
||||
|
||||
const onFetchDataHandler = useCallback(async () => {
|
||||
try {
|
||||
const maxMinTime = GetMaxMinTime({
|
||||
graphType: widget.panelTypes,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
const maxMinTime = GetMaxMinTime({
|
||||
graphType: widget.panelTypes,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
|
||||
const getMinMax = (
|
||||
time: timePreferenceType,
|
||||
): { min: string | number; max: string | number } => {
|
||||
if (time === 'GLOBAL_TIME') {
|
||||
const minMax = GetMinMax(globalSelectedTime);
|
||||
return {
|
||||
min: convertToNanoSecondsToSecond(minMax.minTime / 1000),
|
||||
max: convertToNanoSecondsToSecond(minMax.maxTime / 1000),
|
||||
};
|
||||
}
|
||||
|
||||
const minMax = getStartAndEndTime({
|
||||
type: selectedTime.enum,
|
||||
maxTime: maxMinTime.maxTime,
|
||||
minTime: maxMinTime.minTime,
|
||||
});
|
||||
return { min: parseInt(minMax.start, 10), max: parseInt(minMax.end, 10) };
|
||||
const getMinMax = (
|
||||
time: timePreferenceType,
|
||||
): { min: string | number; max: string | number } => {
|
||||
if (time === 'GLOBAL_TIME') {
|
||||
const minMax = GetMinMax(globalSelectedTime);
|
||||
return {
|
||||
min: convertToNanoSecondsToSecond(minMax.minTime / 1000),
|
||||
max: convertToNanoSecondsToSecond(minMax.maxTime / 1000),
|
||||
};
|
||||
|
||||
const queryMinMax = getMinMax(selectedTime.enum);
|
||||
const response = await Promise.all(
|
||||
widget.query
|
||||
.filter((e) => e.query.length !== 0)
|
||||
.map(async (query) => {
|
||||
const result = await getQueryResult({
|
||||
end: queryMinMax.max.toString(),
|
||||
query: query.query,
|
||||
start: queryMinMax.min.toString(),
|
||||
step: `${getStep({
|
||||
start: queryMinMax.min,
|
||||
end: queryMinMax.max,
|
||||
inputFormat: 's',
|
||||
})}`,
|
||||
});
|
||||
return {
|
||||
query: query.query,
|
||||
queryData: result,
|
||||
legend: query.legend,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const isError = response.find((e) => e.queryData.statusCode !== 200);
|
||||
|
||||
if (isError !== undefined) {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: isError.queryData.error || 'Something went wrong',
|
||||
loading: false,
|
||||
}));
|
||||
} else {
|
||||
const chartDataSet = getChartData({
|
||||
queryData: response.map((e) => ({
|
||||
query: e.query,
|
||||
legend: e.legend,
|
||||
queryData: e.queryData.payload?.result || [],
|
||||
})),
|
||||
});
|
||||
|
||||
setState((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
payload: chartDataSet,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: (error as AxiosError).toString(),
|
||||
loading: false,
|
||||
}));
|
||||
}
|
||||
}, [widget, maxTime, minTime, selectedTime.enum, globalSelectedTime]);
|
||||
|
||||
useEffect(() => {
|
||||
onFetchDataHandler();
|
||||
}, [onFetchDataHandler]);
|
||||
const minMax = getStartAndEndTime({
|
||||
type: selectedTime.enum,
|
||||
maxTime: maxMinTime.maxTime,
|
||||
minTime: maxMinTime.minTime,
|
||||
});
|
||||
return { min: parseInt(minMax.start, 10), max: parseInt(minMax.end, 10) };
|
||||
};
|
||||
|
||||
if (state.error && !state.loading) {
|
||||
return (
|
||||
<NotFoundContainer>
|
||||
<Typography>{state.errorMessage}</Typography>
|
||||
</NotFoundContainer>
|
||||
);
|
||||
const queryMinMax = getMinMax(selectedTime.enum);
|
||||
|
||||
const queryLength = widget.query.filter((e) => e.query.length !== 0);
|
||||
|
||||
const response = useQueries(
|
||||
queryLength.map((query) => {
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
queryFn: () => {
|
||||
return getQueryResult({
|
||||
end: queryMinMax.max.toString(),
|
||||
query: query.query,
|
||||
start: queryMinMax.min.toString(),
|
||||
step: `${getStep({
|
||||
start: queryMinMax.min,
|
||||
end: queryMinMax.max,
|
||||
inputFormat: 's',
|
||||
})}`,
|
||||
});
|
||||
},
|
||||
queryHash: `${query.query}-${query.legend}-${selectedTime.enum}`,
|
||||
retryOnMount: false,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const isError =
|
||||
response.find((e) => e?.data?.statusCode !== 200) !== undefined ||
|
||||
response.some((e) => e.isError === true);
|
||||
|
||||
const isLoading = response.some((e) => e.isLoading === true);
|
||||
|
||||
const errorMessage = response.find((e) => e.data?.error !== null)?.data?.error;
|
||||
|
||||
const data = response.map((responseOfQuery) =>
|
||||
responseOfQuery?.data?.payload?.result.map((e, index) => ({
|
||||
query: queryLength[index]?.query,
|
||||
queryData: e,
|
||||
legend: queryLength[index]?.legend,
|
||||
})),
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height="100%" size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (state.loading || state.payload === undefined) {
|
||||
if (isError || data === undefined || data[0] === undefined) {
|
||||
return (
|
||||
<div>
|
||||
<Spinner height="80vh" size="large" tip="Loading..." />
|
||||
</div>
|
||||
<NotFoundContainer>
|
||||
<Typography>{errorMessage}</Typography>
|
||||
</NotFoundContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -169,17 +136,27 @@ function FullView({
|
||||
setSelectedTime,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={onFetchDataHandler} type="primary">
|
||||
<Button
|
||||
onClick={(): void => {
|
||||
response.forEach((e) => e.refetch());
|
||||
}}
|
||||
type="primary"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</TimeContainer>
|
||||
)}
|
||||
|
||||
{/* <GraphContainer> */}
|
||||
<GridGraphComponent
|
||||
{...{
|
||||
GRAPH_TYPES: widget.panelTypes,
|
||||
data: state.payload,
|
||||
data: getChartData({
|
||||
queryData: data.map((e) => ({
|
||||
query: e?.map((e) => e.query).join(' ') || '',
|
||||
queryData: e?.map((e) => e.queryData) || [],
|
||||
legend: e?.map((e) => e.legend).join('') || '',
|
||||
})),
|
||||
}),
|
||||
isStacked: widget.isStacked,
|
||||
opacity: widget.opacity,
|
||||
title: widget.title,
|
||||
@ -188,18 +165,10 @@ function FullView({
|
||||
yAxisUnit,
|
||||
}}
|
||||
/>
|
||||
{/* </GraphContainer> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface FullViewState {
|
||||
loading: boolean;
|
||||
error: boolean;
|
||||
errorMessage: string;
|
||||
payload: ChartData | undefined;
|
||||
}
|
||||
|
||||
interface FullViewProps {
|
||||
widget: Widgets;
|
||||
fullViewOptions?: boolean;
|
||||
|
@ -65,7 +65,7 @@ function GridCardGraph({
|
||||
.map(async (query) => {
|
||||
const result = await getQueryResult({
|
||||
end,
|
||||
query: query.query,
|
||||
query: encodeURIComponent(query.query),
|
||||
start,
|
||||
step: '60',
|
||||
});
|
||||
|
@ -28,6 +28,7 @@ function GridGraph(): JSX.Element {
|
||||
const { dashboards, loading } = useSelector<AppState, DashboardReducer>(
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [saveLayoutState, setSaveLayoutState] = useState<State>({
|
||||
loading: false,
|
||||
error: false,
|
||||
@ -251,8 +252,13 @@ function GridGraph(): JSX.Element {
|
||||
const isQueryType = type === 'VALUE';
|
||||
|
||||
return (
|
||||
<CardContainer key={rest.i} data-grid={rest}>
|
||||
<Card isQueryType={isQueryType}>
|
||||
<CardContainer
|
||||
isQueryType={isQueryType}
|
||||
isDarkMode={isDarkMode}
|
||||
key={rest.i}
|
||||
data-grid={rest}
|
||||
>
|
||||
<Card isDarkMode={isDarkMode} isQueryType={isQueryType}>
|
||||
<Component />
|
||||
</Card>
|
||||
</CardContainer>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button as ButtonComponent, Card as CardComponent } from 'antd';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import RGL, { WidthProvider } from 'react-grid-layout';
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const ReactGridLayoutComponent = WidthProvider(RGL);
|
||||
|
||||
@ -18,20 +19,34 @@ export const Card = styled(CardComponent)<Props>`
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardContainer = styled.div`
|
||||
.react-resizable-handle {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=');
|
||||
background-position: bottom right;
|
||||
padding: 0 3px 3px 0;
|
||||
background-repeat: no-repeat;
|
||||
background-origin: content-box;
|
||||
box-sizing: border-box;
|
||||
cursor: se-resize;
|
||||
interface Props {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const CardContainer = styled.div<Props>`
|
||||
:hover {
|
||||
.react-resizable-handle {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-position: bottom right;
|
||||
padding: 0 3px 3px 0;
|
||||
background-repeat: no-repeat;
|
||||
background-origin: content-box;
|
||||
box-sizing: border-box;
|
||||
cursor: se-resize;
|
||||
|
||||
${({ isDarkMode }): StyledCSS => {
|
||||
const uri = `data:image/svg+xml,%3Csvg viewBox='0 0 6 6' style='background-color:%23ffffff00' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' x='0px' y='0px' width='6px' height='6px'%0A%3E%3Cg opacity='0.302'%3E%3Cpath d='M 6 6 L 0 6 L 0 4.2 L 4 4.2 L 4.2 4.2 L 4.2 0 L 6 0 L 6 6 L 6 6 Z' fill='${
|
||||
isDarkMode ? 'white' : 'grey'
|
||||
}'/%3E%3C/g%3E%3C/svg%3E`;
|
||||
|
||||
return css`
|
||||
background-image: ${(): string => `url("${uri}")`};
|
||||
`;
|
||||
}}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
function IsRouteAccessible(): JSX.Element {
|
||||
return <div>asd</div>;
|
||||
}
|
||||
|
||||
export default IsRouteAccessible;
|
@ -1,6 +1,7 @@
|
||||
import { notification } from 'antd';
|
||||
import getAll from 'api/alerts/getAll';
|
||||
import Spinner from 'components/Spinner';
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
@ -8,15 +9,37 @@ import ListAlert from './ListAlert';
|
||||
|
||||
function ListAlertRules(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const { data, isError, isLoading, refetch } = useQuery('allAlerts', {
|
||||
const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', {
|
||||
queryFn: getAll,
|
||||
cacheTime: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
||||
notification.error({
|
||||
message: data?.error || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
}, [data?.error, data?.statusCode, status, t]);
|
||||
|
||||
// api failed to load the data
|
||||
if (isError) {
|
||||
return <div>{data?.error || t('something_went_wrong')}</div>;
|
||||
}
|
||||
|
||||
// api is successful but error is present
|
||||
if (status === 'success' && data.statusCode >= 400) {
|
||||
return (
|
||||
<ListAlert
|
||||
{...{
|
||||
allAlertRules: [],
|
||||
refetch,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// in case of loading
|
||||
if (isLoading || !data?.payload) {
|
||||
return <Spinner height="75vh" tip="Loading Rules..." />;
|
||||
}
|
||||
|
@ -77,6 +77,9 @@ function ImportJSON({
|
||||
...queryData,
|
||||
queryData: [],
|
||||
})),
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
||||
import TopEndpointsTable from '../TopEndpointsTable';
|
||||
import { Button, TableContainerCard } from './styles';
|
||||
import { Button } from './styles';
|
||||
|
||||
function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
@ -48,7 +48,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
);
|
||||
};
|
||||
|
||||
const onClickhandler = async (
|
||||
const onClickHandler = async (
|
||||
event: ChartEvent,
|
||||
elements: ActiveElement[],
|
||||
chart: Chart,
|
||||
@ -119,7 +119,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onClickhandler(ChartEvent, activeElements, chart, data, 'Application');
|
||||
onClickHandler(ChartEvent, activeElements, chart, data, 'Application');
|
||||
}}
|
||||
name="application_latency"
|
||||
type="line"
|
||||
@ -189,7 +189,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
name="request_per_sec"
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(event, element, chart, data): void => {
|
||||
onClickhandler(event, element, chart, data, 'Request');
|
||||
onClickHandler(event, element, chart, data, 'Request');
|
||||
}}
|
||||
widget={getWidget([
|
||||
{
|
||||
@ -223,7 +223,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
name="error_percentage_%"
|
||||
fullViewOptions={false}
|
||||
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||
onClickhandler(ChartEvent, activeElements, chart, data, 'Error');
|
||||
onClickHandler(ChartEvent, activeElements, chart, data, 'Error');
|
||||
}}
|
||||
widget={getWidget([
|
||||
{
|
||||
@ -238,9 +238,9 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<TableContainerCard>
|
||||
<Card>
|
||||
<TopEndpointsTable data={topEndPoints} />
|
||||
</TableContainerCard>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
@ -1,8 +1,6 @@
|
||||
import { Button as ButtonComponent } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Card } from '../styles';
|
||||
|
||||
export const Button = styled(ButtonComponent)`
|
||||
&&& {
|
||||
position: absolute;
|
||||
@ -10,6 +8,3 @@ export const Button = styled(ButtonComponent)`
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
export const TableContainerCard = styled(Card)`
|
||||
overflow-x: auto;
|
||||
`;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Table, Tooltip } from 'antd';
|
||||
import { Table, Tooltip, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -51,17 +51,12 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
ellipsis: true,
|
||||
render: (text: string): JSX.Element => (
|
||||
<Tooltip placement="topLeft" title={text}>
|
||||
<Button
|
||||
className="topEndpointsButton"
|
||||
type="link"
|
||||
onClick={(): void => handleOnClick(text)}
|
||||
>
|
||||
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
||||
{text}
|
||||
</Button>
|
||||
</Typography.Link>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
@ -101,9 +96,9 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
|
||||
title={(): string => {
|
||||
return 'Top Endpoints';
|
||||
}}
|
||||
tableLayout="fixed"
|
||||
dataSource={data}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
rowKey="name"
|
||||
/>
|
||||
);
|
||||
|
@ -1,54 +1,108 @@
|
||||
import { Tabs } from 'antd';
|
||||
import RouteTab from 'components/RouteTab';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React from 'react';
|
||||
import { generatePath, useParams } from 'react-router-dom';
|
||||
import { useLocation } from 'react-use';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
import ResourceAttributesFilter from './ResourceAttributesFilter';
|
||||
import Application from './Tabs/Application';
|
||||
import DBCall from './Tabs/DBCall';
|
||||
import External from './Tabs/External';
|
||||
import Overview from './Tabs/Overview';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
const getWidget = (query: Widgets['query']): Widgets => {
|
||||
return {
|
||||
description: '',
|
||||
id: '',
|
||||
isStacked: false,
|
||||
nullZeroValues: '',
|
||||
opacity: '0',
|
||||
panelTypes: 'TIME_SERIES',
|
||||
query,
|
||||
queryData: {
|
||||
data: [],
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
},
|
||||
timePreferance: 'GLOBAL_TIME',
|
||||
title: '',
|
||||
stepSize: 60,
|
||||
};
|
||||
};
|
||||
|
||||
function OverViewTab(): JSX.Element {
|
||||
return <Overview getWidget={getWidget} />;
|
||||
}
|
||||
|
||||
function DbCallTab(): JSX.Element {
|
||||
return <DBCall getWidget={getWidget} />;
|
||||
}
|
||||
|
||||
function ExternalTab(): JSX.Element {
|
||||
return <External getWidget={getWidget} />;
|
||||
}
|
||||
|
||||
function ServiceMetrics(): JSX.Element {
|
||||
const getWidget = (query: Widgets['query']): Widgets => {
|
||||
return {
|
||||
description: '',
|
||||
id: '',
|
||||
isStacked: false,
|
||||
nullZeroValues: '',
|
||||
opacity: '0',
|
||||
panelTypes: 'TIME_SERIES',
|
||||
query,
|
||||
queryData: {
|
||||
data: [],
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
},
|
||||
timePreferance: 'GLOBAL_TIME',
|
||||
title: '',
|
||||
stepSize: 60,
|
||||
};
|
||||
const { search } = useLocation();
|
||||
const { servicename } = useParams<{ servicename: string }>();
|
||||
|
||||
const searchParams = new URLSearchParams(search);
|
||||
const tab = searchParams.get('tab');
|
||||
|
||||
const overMetrics = 'Overview Metrics';
|
||||
const dbCallMetrics = 'Database Calls';
|
||||
const externalMetrics = 'External Calls';
|
||||
|
||||
const getActiveKey = (): string => {
|
||||
if (tab === null) {
|
||||
return overMetrics;
|
||||
}
|
||||
|
||||
if (tab === dbCallMetrics) {
|
||||
return dbCallMetrics;
|
||||
}
|
||||
|
||||
if (tab === externalMetrics) {
|
||||
return externalMetrics;
|
||||
}
|
||||
|
||||
return overMetrics;
|
||||
};
|
||||
|
||||
const activeKey = getActiveKey();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResourceAttributesFilter />
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane animated destroyInactiveTabPane tab="Application Metrics" key="1">
|
||||
<Application getWidget={getWidget} />
|
||||
</TabPane>
|
||||
|
||||
<TabPane animated destroyInactiveTabPane tab="External Calls" key="2">
|
||||
<External getWidget={getWidget} />
|
||||
</TabPane>
|
||||
|
||||
<TabPane animated destroyInactiveTabPane tab="Database Calls" key="3">
|
||||
<DBCall getWidget={getWidget} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
<RouteTab
|
||||
routes={[
|
||||
{
|
||||
Component: OverViewTab,
|
||||
name: overMetrics,
|
||||
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
||||
servicename,
|
||||
})}?tab=${overMetrics}`,
|
||||
},
|
||||
{
|
||||
Component: DbCallTab,
|
||||
name: dbCallMetrics,
|
||||
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
||||
servicename,
|
||||
})}?tab=${dbCallMetrics}`,
|
||||
},
|
||||
{
|
||||
Component: ExternalTab,
|
||||
name: externalMetrics,
|
||||
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
||||
servicename,
|
||||
})}?tab=${externalMetrics}`,
|
||||
},
|
||||
]}
|
||||
activeKey={activeKey}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServiceMetrics;
|
||||
export default React.memo(ServiceMetrics);
|
||||
|
@ -77,7 +77,6 @@ function Metrics(): JSX.Element {
|
||||
loading={loading}
|
||||
dataSource={services}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
rowKey="serviceName"
|
||||
/>
|
||||
</Container>
|
||||
|
@ -12,9 +12,23 @@ function ShareModal({
|
||||
onToggleHandler,
|
||||
selectedData,
|
||||
}: ShareModalProps): JSX.Element {
|
||||
const [jsonValue, setJSONValue] = useState<string>(
|
||||
JSON.stringify(selectedData, null, 2),
|
||||
);
|
||||
const getParsedValue = (): string => {
|
||||
const updatedData: DashboardData = {
|
||||
...selectedData,
|
||||
widgets: selectedData.widgets?.map((widget) => ({
|
||||
...widget,
|
||||
queryData: {
|
||||
...widget.queryData,
|
||||
loading: false,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
},
|
||||
})),
|
||||
};
|
||||
return JSON.stringify(updatedData, null, 2);
|
||||
};
|
||||
|
||||
const [jsonValue, setJSONValue] = useState<string>(getParsedValue());
|
||||
const [isViewJSON, setIsViewJSON] = useState<boolean>(false);
|
||||
const { t } = useTranslation(['dashboard', 'common']);
|
||||
const [state, setCopy] = useCopyToClipboard();
|
||||
|
@ -5,6 +5,7 @@ import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
|
||||
import { NewWidgetProps } from '../../index';
|
||||
@ -18,6 +19,7 @@ function WidgetGraph({
|
||||
const { dashboards, isQueryFired } = useSelector<AppState, DashboardReducer>(
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [selectedDashboard] = dashboards;
|
||||
const { search } = useLocation();
|
||||
|
||||
@ -31,7 +33,11 @@ function WidgetGraph({
|
||||
const selectedWidget = widgets.find((e) => e.id === widgetId);
|
||||
|
||||
if (selectedWidget === undefined) {
|
||||
return <Card isQueryType={false}>Invalid widget</Card>;
|
||||
return (
|
||||
<Card isDarkMode={isDarkMode} isQueryType={false}>
|
||||
Invalid widget
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const { queryData } = selectedWidget;
|
||||
|
@ -38,7 +38,7 @@ function DisplayName({
|
||||
dispatch({
|
||||
type: UPDATE_ORG_NAME,
|
||||
payload: {
|
||||
index,
|
||||
orgId,
|
||||
name: orgName,
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons';
|
||||
import { Menu, Typography } from 'antd';
|
||||
import { Menu, Space, Typography } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -19,6 +19,7 @@ import {
|
||||
Sider,
|
||||
SlackButton,
|
||||
SlackMenuItemContainer,
|
||||
Tags,
|
||||
VersionContainer,
|
||||
} from './styles';
|
||||
|
||||
@ -96,13 +97,21 @@ function SideNav(): JSX.Element {
|
||||
selectedKeys={[pathname]}
|
||||
mode="inline"
|
||||
>
|
||||
{menus.map(({ to, Icon, name }) => (
|
||||
{menus.map(({ to, Icon, name, tags }) => (
|
||||
<Menu.Item
|
||||
key={to}
|
||||
icon={<Icon />}
|
||||
onClick={(): void => onClickHandler(to)}
|
||||
>
|
||||
<Typography>{name}</Typography>
|
||||
<Space style={{ position: 'relative' }}>
|
||||
<Typography>{name}</Typography>
|
||||
{tags &&
|
||||
tags.map((e) => (
|
||||
<Tags color="#177DDC" key={e}>
|
||||
<Typography.Text strong>{e}</Typography.Text>
|
||||
</Tags>
|
||||
))}
|
||||
</Space>
|
||||
</Menu.Item>
|
||||
))}
|
||||
{sidebar.map((props, index) => (
|
||||
|
@ -41,6 +41,7 @@ const menus: SidebarMenu[] = [
|
||||
to: ROUTES.SERVICE_MAP,
|
||||
name: 'Service Map',
|
||||
Icon: DeploymentUnitOutlined,
|
||||
tags: ['Beta'],
|
||||
},
|
||||
{
|
||||
Icon: LineChartOutlined,
|
||||
@ -63,6 +64,7 @@ interface SidebarMenu {
|
||||
to: string;
|
||||
name: string;
|
||||
Icon: typeof ApiOutlined;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export default menus;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Layout, Typography } from 'antd';
|
||||
import { Layout, Tag, Typography } from 'antd';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
@ -75,3 +75,11 @@ export const VersionContainer = styled.div`
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Tags = styled(Tag)`
|
||||
&&& {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Checkbox, notification, Typography } from 'antd';
|
||||
import { Checkbox, notification, Tooltip, Typography } from 'antd';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { AxiosError } from 'axios';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { getFilter, updateURL } from 'store/actions/trace/util';
|
||||
@ -11,7 +11,7 @@ import { UPDATE_ALL_FILTERS } from 'types/actions/trace';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { CheckBoxContainer } from './styles';
|
||||
import { CheckBoxContainer, ParaGraph } from './styles';
|
||||
|
||||
function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
const {
|
||||
@ -155,6 +155,11 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
|
||||
const isCheckBoxSelected = isUserSelected;
|
||||
|
||||
const TooTipOverLay = useMemo(
|
||||
(): JSX.Element => <Typography>{keyValue}</Typography>,
|
||||
[keyValue],
|
||||
);
|
||||
|
||||
return (
|
||||
<CheckBoxContainer>
|
||||
<Checkbox
|
||||
@ -164,7 +169,9 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
|
||||
defaultChecked
|
||||
key={keyValue}
|
||||
>
|
||||
{keyValue}
|
||||
<Tooltip overlay={TooTipOverLay}>
|
||||
<ParaGraph ellipsis>{keyValue}</ParaGraph>
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
{isCheckBoxSelected ? (
|
||||
<Typography>{value}</Typography>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const CheckBoxContainer = styled.div`
|
||||
@ -9,3 +10,10 @@ export const CheckBoxContainer = styled.div`
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`;
|
||||
|
||||
export const ParaGraph = styled(Typography.Paragraph)`
|
||||
&&& {
|
||||
margin: 0;
|
||||
max-width: 8rem;
|
||||
}
|
||||
`;
|
||||
|
@ -34,7 +34,7 @@ function TraceGraph(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || payload === undefined) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Container>
|
||||
<Spinner height="20vh" size="small" tip="Loading..." />
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { notification } from 'antd';
|
||||
import getTriggeredApi from 'api/alerts/getTriggered';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PayloadProps } from 'types/api/alerts/getTriggered';
|
||||
|
||||
import TriggerComponent from './TriggeredAlert';
|
||||
@ -14,6 +16,7 @@ function TriggeredAlerts(): JSX.Element {
|
||||
success: false,
|
||||
payload: [],
|
||||
});
|
||||
const { t } = useTranslation(['common']);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
@ -56,8 +59,16 @@ function TriggeredAlerts(): JSX.Element {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupState.error) {
|
||||
notification.error({
|
||||
message: groupState.errorMessage || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
}, [groupState.error, groupState.errorMessage, t]);
|
||||
|
||||
if (groupState.error) {
|
||||
return <div>{groupState.errorMessage}</div>;
|
||||
return <TriggerComponent allAlerts={[]} />;
|
||||
}
|
||||
|
||||
if (groupState.loading || groupState.payload === undefined) {
|
||||
|
@ -5,12 +5,13 @@ 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 { connect, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import SelectService from './SelectService';
|
||||
import { getGraphData, getTooltip, getZoomPx, transformLabel } from './utils';
|
||||
@ -53,6 +54,8 @@ export interface graphDataType {
|
||||
function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||
const fgRef = useRef();
|
||||
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const { getDetailedServiceMapItems, globalTime, serviceMap } = props;
|
||||
|
||||
useEffect(() => {
|
||||
@ -115,10 +118,11 @@ function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||
ctx.fillStyle = node.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = isDarkMode ? '#3d0b00' : '#ffbcad';
|
||||
ctx.fill();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = '#646464';
|
||||
ctx.fillStyle = isDarkMode ? '#ffffff' : '#000000';
|
||||
ctx.fillText(label, node.x, node.y);
|
||||
}}
|
||||
onNodeClick={(node) => {
|
||||
|
@ -5,6 +5,7 @@ import Spinner from 'components/Spinner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ErrorDetailsContainer from 'container/ErrorDetails';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Redirect, useLocation } from 'react-router-dom';
|
||||
@ -13,6 +14,7 @@ import { PayloadProps } from 'types/api/errors/getById';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
function ErrorDetails(): JSX.Element {
|
||||
const { t } = useTranslation(['common']);
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
@ -22,6 +24,7 @@ function ErrorDetails(): JSX.Element {
|
||||
const errorId = params.get('errorId');
|
||||
const errorType = params.get('errorType');
|
||||
const serviceName = params.get('serviceName');
|
||||
const defaultError = t('something_went_wrong');
|
||||
|
||||
const { data, status } = useQuery(
|
||||
[
|
||||
@ -72,16 +75,31 @@ function ErrorDetails(): JSX.Element {
|
||||
},
|
||||
);
|
||||
|
||||
// if errorType and serviceName is null redirecting to the ALL_ERROR page not now
|
||||
if (errorType === null || serviceName === null) {
|
||||
return <Redirect to={ROUTES.ALL_ERROR} />;
|
||||
}
|
||||
|
||||
// when the api is in loading state
|
||||
if (status === 'loading' || ErrorIdStatus === 'loading') {
|
||||
return <Spinner tip="Loading.." />;
|
||||
}
|
||||
|
||||
// if any error occurred while loading
|
||||
if (status === 'error' || ErrorIdStatus === 'error') {
|
||||
return <Typography>{data?.error || errorIdPayload?.error}</Typography>;
|
||||
return (
|
||||
<Typography>
|
||||
{data?.error || errorIdPayload?.error || defaultError}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// if API is successfully but there is an error
|
||||
if (
|
||||
(status === 'success' && data?.statusCode >= 400) ||
|
||||
(ErrorIdStatus === 'success' && errorIdPayload.statusCode >= 400)
|
||||
) {
|
||||
return <Typography>{data?.error || defaultError}</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -20,7 +20,10 @@ function Login(): JSX.Element {
|
||||
enabled: !isLoggedIn,
|
||||
});
|
||||
|
||||
if (versionResult.status === 'error') {
|
||||
if (
|
||||
versionResult.status === 'error' ||
|
||||
(versionResult.status === 'success' && versionResult?.data.statusCode !== 200)
|
||||
) {
|
||||
return (
|
||||
<Typography>
|
||||
{versionResult.data?.error || t('something_went_wrong')}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { notification } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||
@ -20,10 +21,20 @@ function Metrics({ getService }: MetricsProps): JSX.Element {
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
const { services, resourceAttributeQueries } = useSelector<
|
||||
AppState,
|
||||
MetricReducer
|
||||
>((state) => state.metrics);
|
||||
const {
|
||||
services,
|
||||
resourceAttributeQueries,
|
||||
error,
|
||||
errorMessage,
|
||||
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
notification.error({
|
||||
message: errorMessage,
|
||||
});
|
||||
}
|
||||
}, [error, errorMessage]);
|
||||
|
||||
const selectedTags = useMemo(
|
||||
() =>
|
||||
|
@ -10,8 +10,8 @@ export const Container = styled.div`
|
||||
|
||||
export const LeftContainer = styled(Card)`
|
||||
flex: 0.5;
|
||||
width: 95%;
|
||||
padding-right: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
width: 15rem;
|
||||
|
||||
.ant-card-body {
|
||||
padding: 0;
|
||||
|
@ -7,6 +7,7 @@ import AppActions from 'types/actions';
|
||||
import {
|
||||
UPDATE_ALL_FILTERS,
|
||||
UPDATE_TRACE_FILTER_LOADING,
|
||||
UPDATE_TRACE_GRAPH_LOADING,
|
||||
} from 'types/actions/trace';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
@ -183,6 +184,12 @@ export const GetInitialTraceFilter = (
|
||||
filterLoading: false,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_GRAPH_LOADING,
|
||||
payload: {
|
||||
loading: false,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch({
|
||||
@ -191,6 +198,12 @@ export const GetInitialTraceFilter = (
|
||||
filterLoading: false,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_GRAPH_LOADING,
|
||||
payload: {
|
||||
loading: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { applyMiddleware, compose, createStore } from 'redux';
|
||||
import thunk, { ThunkMiddleware } from 'redux-thunk';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
import reducers, { AppState } from './reducers';
|
||||
|
||||
@ -8,8 +9,9 @@ const composeEnhancers =
|
||||
|
||||
const store = createStore(
|
||||
reducers,
|
||||
// @TODO Add Type for AppActions also
|
||||
composeEnhancers(applyMiddleware(thunk as ThunkMiddleware<AppState>)),
|
||||
composeEnhancers(
|
||||
applyMiddleware(thunk as ThunkMiddleware<AppState, AppActions>),
|
||||
),
|
||||
);
|
||||
|
||||
export default store;
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_LATEST_VERSION,
|
||||
UPDATE_LATEST_VERSION_ERROR,
|
||||
UPDATE_ORG,
|
||||
UPDATE_ORG_NAME,
|
||||
UPDATE_USER,
|
||||
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
|
||||
@ -172,16 +173,19 @@ const appReducer = (
|
||||
|
||||
case UPDATE_ORG_NAME: {
|
||||
const stateOrg = state.org || ({} as OrgPayload);
|
||||
const { index, name: updatedName } = action.payload;
|
||||
const current = stateOrg[index];
|
||||
const { orgId, name: updatedName } = action.payload;
|
||||
|
||||
const orgIndex = stateOrg.findIndex((e) => e.id === orgId);
|
||||
|
||||
const current = stateOrg[orgIndex];
|
||||
|
||||
const updatedOrg: OrgPayload = [
|
||||
...stateOrg.slice(0, index),
|
||||
...stateOrg.slice(0, orgIndex),
|
||||
{
|
||||
...current,
|
||||
name: updatedName,
|
||||
},
|
||||
...stateOrg.slice(index + 1, stateOrg.length),
|
||||
...stateOrg.slice(orgIndex + 1, stateOrg.length),
|
||||
];
|
||||
|
||||
return {
|
||||
@ -190,6 +194,13 @@ const appReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_ORG: {
|
||||
return {
|
||||
...state,
|
||||
org: action.payload.org,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export const UPDATE_USER_IS_FETCH = 'UPDATE_USER_IS_FETCH';
|
||||
export const UPDATE_USER_ORG_ROLE = 'UPDATE_USER_ORG_ROLE';
|
||||
export const UPDATE_USER = 'UPDATE_USER';
|
||||
export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
|
||||
export const UPDATE_ORG = 'UPDATE_ORG';
|
||||
|
||||
export interface SwitchDarkMode {
|
||||
type: typeof SWITCH_DARK_MODE;
|
||||
@ -98,7 +99,14 @@ export interface UpdateOrgName {
|
||||
type: typeof UPDATE_ORG_NAME;
|
||||
payload: {
|
||||
name: string;
|
||||
index: number;
|
||||
orgId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateOrg {
|
||||
type: typeof UPDATE_ORG;
|
||||
payload: {
|
||||
org: AppReducer['org'];
|
||||
};
|
||||
}
|
||||
|
||||
@ -113,4 +121,5 @@ export type AppAction =
|
||||
| UpdateUserIsFetched
|
||||
| UpdateUserOrgRole
|
||||
| UpdateUser
|
||||
| UpdateOrgName;
|
||||
| UpdateOrgName
|
||||
| UpdateOrg;
|
||||
|
@ -3,6 +3,7 @@ declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
FRONTEND_API_ENDPOINT: string | undefined;
|
||||
NODE_ENV: 'development' | 'production' | 'test';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@
|
||||
"noEmit": true,
|
||||
"baseUrl": "./src",
|
||||
"downlevelIteration": true,
|
||||
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||
"plugins": [{ "name": "typescript-plugin-css-modules" }],
|
||||
"types": ["cypress", "@testing-library/cypress", "node"]
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": [
|
||||
@ -31,6 +32,9 @@
|
||||
"./conf/default.conf",
|
||||
"./public",
|
||||
"./tests",
|
||||
"playwright.config.ts"
|
||||
"playwright.config.ts",
|
||||
"./commitlint.config.js",
|
||||
"./webpack.config.js",
|
||||
"./webpack.config.prod.js"
|
||||
]
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
// shared config (dev and prod)
|
||||
const { resolve } = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
@ -5,8 +6,7 @@ const portFinderSync = require('portfinder-sync');
|
||||
const dotenv = require('dotenv');
|
||||
const webpack = require('webpack');
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
||||
.BundleAnalyzerPlugin;
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@ -106,7 +106,7 @@ const config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: plugins,
|
||||
plugins,
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
// shared config (dev and prod)
|
||||
const { resolve } = require('path');
|
||||
@ -9,8 +10,7 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
||||
.BundleAnalyzerPlugin;
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const Critters = require('critters-webpack-plugin');
|
||||
|
||||
const plugins = [
|
||||
@ -119,13 +119,13 @@ const config = {
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: plugins,
|
||||
plugins,
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
concatenateModules: false,
|
||||
emitOnErrors: true,
|
||||
flagIncludedChunks: true,
|
||||
innerGraph: true, //tells webpack whether to conduct inner graph analysis for unused exports.
|
||||
innerGraph: true, // tells webpack whether to conduct inner graph analysis for unused exports.
|
||||
mangleWasmImports: true,
|
||||
mergeDuplicateChunks: true,
|
||||
minimize: true,
|
||||
|
3385
frontend/yarn.lock
3385
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -2333,6 +2333,40 @@ func (r *ClickHouseReader) setColdStorage(ctx context.Context, tableName string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) RemoveTTL(ctx context.Context,
|
||||
params *model.RemoveTTLParams) (*model.RemoveTTLResponseItem, *model.ApiError) {
|
||||
|
||||
var reqs []string
|
||||
templateQuery := `ALTER TABLE %v REMOVE TTL`
|
||||
tracesTables := []string{signozTraceDBName + "." + signozTraceTableName, signozTraceDBName + "." + signozDurationMVTable, signozTraceDBName + "." + signozSpansTable, signozTraceDBName + "." + signozErrorIndexTable}
|
||||
metricsTables := []string{signozMetricDBName + "." + signozSampleName}
|
||||
|
||||
switch params.Type {
|
||||
case constants.TraceTTL:
|
||||
for _, tableName := range tracesTables {
|
||||
reqs = append(reqs, fmt.Sprintf(templateQuery, tableName))
|
||||
}
|
||||
case constants.MetricsTTL:
|
||||
for _, tableName := range metricsTables {
|
||||
reqs = append(reqs, fmt.Sprintf(templateQuery, tableName))
|
||||
}
|
||||
default:
|
||||
for _, tableName := range append(append([]string{}, tracesTables...), metricsTables...) {
|
||||
reqs = append(reqs, fmt.Sprintf(templateQuery, tableName))
|
||||
}
|
||||
}
|
||||
|
||||
zap.S().Debugf("Executing remove TTL requests: %s\n", reqs)
|
||||
for _, req := range reqs {
|
||||
if err := r.db.Exec(ctx, req); err != nil {
|
||||
zap.S().Error(fmt.Errorf("error while removing ttl. Err=%v", err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorExec,
|
||||
Err: fmt.Errorf("error while removing ttl. Err=%v", err)}
|
||||
}
|
||||
}
|
||||
return &model.RemoveTTLResponseItem{Message: "ttl has been successfully removed"}, nil
|
||||
}
|
||||
|
||||
// GetDisks returns a list of disks {name, type} configured in clickhouse DB.
|
||||
func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *model.ApiError) {
|
||||
diskItems := []model.DiskItem{}
|
||||
|
@ -310,6 +310,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/api/v1/serviceMapDependencies", ViewAccess(aH.serviceMapDependencies)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/settings/ttl", AdminAccess(aH.setTTL)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/settings/ttl", ViewAccess(aH.getTTL)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/settings/ttl", AdminAccess(aH.removeTTL)).Methods(http.MethodDelete)
|
||||
|
||||
router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
||||
|
||||
@ -1162,6 +1163,47 @@ func (aH *APIHandler) getTTL(w http.ResponseWriter, r *http.Request) {
|
||||
aH.writeJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) removeTTL(w http.ResponseWriter, r *http.Request) {
|
||||
ttlParams, err := parseRemoveTTL(r)
|
||||
if aH.handleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
existingTTL, apiErr := (*aH.reader).GetTTL(context.Background(), &model.GetTTLParams{GetAllTTL: true})
|
||||
if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
|
||||
if ttlParams.Type == constants.TraceTTL && existingTTL.TracesTime == -1 &&
|
||||
aH.handleError(w, fmt.Errorf("traces doesn't have any TTL set, cannot remove"), http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
if ttlParams.Type == constants.MetricsTTL && existingTTL.MetricsTime == -1 &&
|
||||
aH.handleError(w, fmt.Errorf("metrics doesn't have any TTL set, cannot remove"), http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
if ttlParams.RemoveAllTTL {
|
||||
if existingTTL.TracesTime == -1 && existingTTL.MetricsTime != -1 {
|
||||
ttlParams.Type = constants.MetricsTTL
|
||||
ttlParams.RemoveAllTTL = false
|
||||
} else if existingTTL.TracesTime != -1 && existingTTL.MetricsTime == -1 {
|
||||
ttlParams.Type = constants.TraceTTL
|
||||
ttlParams.RemoveAllTTL = false
|
||||
} else if aH.handleError(w, fmt.Errorf("no TTL set, cannot remove"), http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result, apiErr := (*aH.reader).RemoveTTL(context.Background(), ttlParams)
|
||||
if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
|
||||
aH.writeJSON(w, r, result)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getDisks(w http.ResponseWriter, r *http.Request) {
|
||||
result, apiErr := (*aH.reader).GetDisks(context.Background())
|
||||
if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) {
|
||||
|
@ -50,6 +50,7 @@ type Reader interface {
|
||||
|
||||
// Setter Interfaces
|
||||
SetTTL(ctx context.Context, ttlParams *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError)
|
||||
RemoveTTL(ctx context.Context, ttlParams *model.RemoveTTLParams) (*model.RemoveTTLResponseItem, *model.ApiError)
|
||||
|
||||
GetMetricAutocompleteMetricNames(ctx context.Context, matchText string) (*[]string, *model.ApiError)
|
||||
GetMetricAutocompleteTagKey(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError)
|
||||
|
@ -545,7 +545,7 @@ func parseTTLParams(r *http.Request) (*model.TTLParams, error) {
|
||||
|
||||
// Validate the TTL duration.
|
||||
durationParsed, err := time.ParseDuration(delDuration)
|
||||
if err != nil {
|
||||
if err != nil || durationParsed.Seconds() <= 0 {
|
||||
return nil, fmt.Errorf("Not a valid TTL duration %v", delDuration)
|
||||
}
|
||||
|
||||
@ -554,7 +554,7 @@ func parseTTLParams(r *http.Request) (*model.TTLParams, error) {
|
||||
// If some cold storage is provided, validate the cold storage move TTL.
|
||||
if len(coldStorage) > 0 {
|
||||
toColdParsed, err = time.ParseDuration(toColdDuration)
|
||||
if err != nil {
|
||||
if err != nil || toColdParsed.Seconds() <= 0 {
|
||||
return nil, fmt.Errorf("Not a valid toCold TTL duration %v", toColdDuration)
|
||||
}
|
||||
if toColdParsed.Seconds() != 0 && toColdParsed.Seconds() >= durationParsed.Seconds() {
|
||||
@ -587,6 +587,23 @@ func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) {
|
||||
return &model.GetTTLParams{Type: typeTTL, GetAllTTL: getAllTTL}, nil
|
||||
}
|
||||
|
||||
func parseRemoveTTL(r *http.Request) (*model.RemoveTTLParams, error) {
|
||||
|
||||
typeTTL := r.URL.Query().Get("type")
|
||||
removeAllTTL := false
|
||||
|
||||
if len(typeTTL) == 0 {
|
||||
removeAllTTL = true
|
||||
} else {
|
||||
// Validate the type parameter
|
||||
if typeTTL != constants.TraceTTL && typeTTL != constants.MetricsTTL {
|
||||
return nil, fmt.Errorf("type param should be <metrics|traces>, got %v", typeTTL)
|
||||
}
|
||||
}
|
||||
|
||||
return &model.RemoveTTLParams{Type: typeTTL, RemoveAllTTL: removeAllTTL}, nil
|
||||
}
|
||||
|
||||
func parseUserRequest(r *http.Request) (*model.User, error) {
|
||||
var req model.User
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof" // http profiler
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@ -257,7 +258,15 @@ func (s *Server) Start() error {
|
||||
zap.S().Error("Could not start HTTP server", zap.Error(err))
|
||||
}
|
||||
s.unavailableChannel <- healthcheck.Unavailable
|
||||
}()
|
||||
|
||||
go func() {
|
||||
zap.S().Info("Starting pprof server", zap.String("addr", constants.DebugHttpPort))
|
||||
|
||||
err = http.ListenAndServe(constants.DebugHttpPort, nil)
|
||||
if err != nil {
|
||||
zap.S().Error("Could not start pprof server", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
@ -6,7 +6,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
HTTPHostPort = "0.0.0.0:8080"
|
||||
HTTPHostPort = "0.0.0.0:8080" // Address to serve http (query service)
|
||||
DebugHttpPort = "0.0.0.0:6060" // Address to serve http (pprof)
|
||||
)
|
||||
|
||||
var DEFAULT_TELEMETRY_ANONYMOUS = false
|
||||
|
@ -206,3 +206,8 @@ type GetErrorParams struct {
|
||||
ErrorID string
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
type RemoveTTLParams struct {
|
||||
Type string
|
||||
RemoveAllTTL bool
|
||||
}
|
||||
|
@ -246,6 +246,10 @@ type SetTTLResponseItem struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type RemoveTTLResponseItem struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type DiskItem struct {
|
||||
Name string `json:"name,omitempty" ch:"name"`
|
||||
Type string `json:"type,omitempty" ch:"type"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user