fix: merge conflit resolved

This commit is contained in:
Palash gupta 2022-05-20 13:29:08 +05:30
commit cbaf9b009c
No known key found for this signature in database
GPG Key ID: 8FD05AE6F9150AD6
63 changed files with 2594 additions and 1768 deletions

View File

@ -106,32 +106,70 @@ Need to update [https://github.com/SigNoz/charts](https://github.com/SigNoz/char
- [k3d](https://k3d.io/#installation) - [k3d](https://k3d.io/#installation)
- [minikube](https://minikube.sigs.k8s.io/docs/start/) - [minikube](https://minikube.sigs.k8s.io/docs/start/)
- create a k8s cluster and make sure `kubectl` points to the locally created k8s cluster - 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 `make dev-install` to install SigNoz chart with `my-release` release name in `platform` namespace.
- run `kubectl -n platform port-forward svc/my-release-frontend 3301:3301` to make SigNoz UI available at [localhost:3301](http://localhost:3301) - 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:** **To load data with HotROD sample app:**
```sh ```bash
kubectl create ns sample-application
kubectl -n sample-application apply -f https://raw.githubusercontent.com/SigNoz/signoz/main/sample-apps/hotrod/hotrod.yaml
kubectl -n sample-application run strzal --image=djbingham/curl \ kubectl -n sample-application run strzal --image=djbingham/curl \
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \ --restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm 'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
``` ```
**To stop the load generation:** **To stop the load generation:**
```sh ```bash
kubectl -n sample-application run strzal --image=djbingham/curl \ kubectl -n sample-application run strzal --image=djbingham/curl \
--restart='OnFailure' -i --tty --rm --command -- curl \ --restart='OnFailure' -i --tty --rm --command -- curl \
http://locust-master:8089/stop 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 ## 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). 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 - If you find any bugs, please create an issue

View File

@ -39,8 +39,9 @@ services:
query-service: query-service:
image: signoz/query-service:0.8.0 image: signoz/query-service:0.8.0
command: ["-config=/root/config/prometheus.yml"] command: ["-config=/root/config/prometheus.yml"]
ports: # ports:
- "8080:8080" # - "6060:6060" # pprof port
# - "8080:8080" # query-service port
volumes: volumes:
- ./prometheus.yml:/root/config/prometheus.yml - ./prometheus.yml:/root/config/prometheus.yml
- ../dashboards:/root/config/dashboards - ../dashboards:/root/config/dashboards
@ -85,7 +86,7 @@ services:
- "4317:4317" # OTLP gRPC receiver - "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver - "4318:4318" # OTLP HTTP receiver
# - "8889:8889" # Prometheus metrics exposed by the agent # - "8889:8889" # Prometheus metrics exposed by the agent
# - "13133" # health_check # - "13133:13133" # health_check
# - "14268:14268" # Jaeger receiver # - "14268:14268" # Jaeger receiver
# - "55678:55678" # OpenCensus receiver # - "55678:55678" # OpenCensus receiver
# - "55679:55679" # zpages extension # - "55679:55679" # zpages extension

View File

@ -12,13 +12,18 @@ server {
gzip_http_version 1.1; gzip_http_version 1.1;
location / { location / {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0"; if ( $uri = '/index.html' ) {
add_header Last-Modified $date_gmt; add_header Cache-Control no-store always;
}
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html index.htm; index index.html index.htm;
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
location /api/alertmanager {
proxy_pass http://alertmanager:9093/api/v2;
}
location /api { location /api {
proxy_pass http://query-service:8080/api; proxy_pass http://query-service:8080/api;
} }

View File

@ -40,6 +40,9 @@ services:
image: signoz/query-service:0.8.0 image: signoz/query-service:0.8.0
container_name: query-service container_name: query-service
command: ["-config=/root/config/prometheus.yml"] command: ["-config=/root/config/prometheus.yml"]
# ports:
# - "6060:6060" # pprof port
# - "8080:8080" # query-service port
volumes: volumes:
- ./prometheus.yml:/root/config/prometheus.yml - ./prometheus.yml:/root/config/prometheus.yml
- ../dashboards:/root/config/dashboards - ../dashboards:/root/config/dashboards
@ -82,7 +85,7 @@ services:
- "4317:4317" # OTLP gRPC receiver - "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver - "4318:4318" # OTLP HTTP receiver
# - "8889:8889" # Prometheus metrics exposed by the agent # - "8889:8889" # Prometheus metrics exposed by the agent
# - "13133" # health_check # - "13133:13133" # health_check
# - "14268:14268" # Jaeger receiver # - "14268:14268" # Jaeger receiver
# - "55678:55678" # OpenCensus receiver # - "55678:55678" # OpenCensus receiver
# - "55679:55679" # zpages extension # - "55679:55679" # zpages extension

View File

@ -39,6 +39,9 @@ services:
image: signoz/query-service:0.8.0 image: signoz/query-service:0.8.0
container_name: query-service container_name: query-service
command: ["-config=/root/config/prometheus.yml"] command: ["-config=/root/config/prometheus.yml"]
# ports:
# - "6060:6060" # pprof port
# - "8080:8080" # query-service port
volumes: volumes:
- ./prometheus.yml:/root/config/prometheus.yml - ./prometheus.yml:/root/config/prometheus.yml
- ../dashboards:/root/config/dashboards - ../dashboards:/root/config/dashboards
@ -80,7 +83,7 @@ services:
- "4317:4317" # OTLP gRPC receiver - "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver - "4318:4318" # OTLP HTTP receiver
# - "8889:8889" # Prometheus metrics exposed by the agent # - "8889:8889" # Prometheus metrics exposed by the agent
# - "13133" # health_check # - "13133:13133" # health_check
# - "14268:14268" # Jaeger receiver # - "14268:14268" # Jaeger receiver
# - "55678:55678" # OpenCensus receiver # - "55678:55678" # OpenCensus receiver
# - "55679:55679" # zpages extension # - "55679:55679" # zpages extension

View File

@ -12,14 +12,15 @@ server {
gzip_http_version 1.1; gzip_http_version 1.1;
location / { location / {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0"; if ( $uri = '/index.html' ) {
add_header Last-Modified $date_gmt; add_header Cache-Control no-store always;
}
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html index.htm; index index.html index.htm;
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
location /api/alertmanager{ location /api/alertmanager {
proxy_pass http://alertmanager:9093/api/v2; proxy_pass http://alertmanager:9093/api/v2;
} }

4
frontend/.husky/commit-msg Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd frontend && npm run commitlint

View File

@ -1 +1 @@
12.13.0 16.15.0

View File

@ -12,6 +12,9 @@ WORKDIR /frontend
# copy the package.json to install dependencies # copy the package.json to install dependencies
COPY package.json ./ COPY package.json ./
# configure node_env as production
ENV NODE_ENV=production
# Install the dependencies and make the folder # Install the dependencies and make the folder
RUN yarn install RUN yarn install

View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };

View File

@ -13,12 +13,13 @@
"jest:coverage": "jest --coverage", "jest:coverage": "jest --coverage",
"jest:watch": "jest --watch", "jest:watch": "jest --watch",
"postinstall": "yarn husky:configure", "postinstall": "yarn husky:configure",
"husky:configure": "cd .. && husky install frontend/.husky",
"playwright": "playwright test --config=./playwright.config.ts", "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": { "engines": {
"node": ">=12.13.0" "node": ">=16.15.0"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
@ -108,6 +109,8 @@
"@babel/preset-env": "^7.12.17", "@babel/preset-env": "^7.12.17",
"@babel/preset-react": "^7.12.13", "@babel/preset-react": "^7.12.13",
"@babel/preset-typescript": "^7.12.17", "@babel/preset-typescript": "^7.12.17",
"@commitlint/cli": "^16.2.4",
"@commitlint/config-conventional": "^16.2.4",
"@jest/globals": "^27.5.1", "@jest/globals": "^27.5.1",
"@playwright/test": "^1.22.0", "@playwright/test": "^1.22.0",
"@testing-library/react-hooks": "^7.0.2", "@testing-library/react-hooks": "^7.0.2",
@ -135,7 +138,7 @@
"@typescript-eslint/parser": "^4.28.2", "@typescript-eslint/parser": "^4.28.2",
"autoprefixer": "^9.0.0", "autoprefixer": "^9.0.0",
"babel-plugin-styled-components": "^1.12.0", "babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "^9.0.0", "compression-webpack-plugin": "9.0.0",
"copy-webpack-plugin": "^8.1.0", "copy-webpack-plugin": "^8.1.0",
"critters-webpack-plugin": "^3.0.1", "critters-webpack-plugin": "^3.0.1",
"eslint": "^7.30.0", "eslint": "^7.30.0",

View File

@ -2,5 +2,8 @@
"general": "General", "general": "General",
"alert_channels": "Alert Channels", "alert_channels": "Alert Channels",
"organization_settings": "Organization Settings", "organization_settings": "Organization Settings",
"my_settings": "My Settings" "my_settings": "My Settings",
"overview_metrics": "Overview Metrics",
"dbcall_metrics": "Database Calls",
"external_metrics": "External Calls"
} }

View File

@ -2,5 +2,8 @@
"general": "General", "general": "General",
"alert_channels": "Alert Channels", "alert_channels": "Alert Channels",
"organization_settings": "Organization Settings", "organization_settings": "Organization Settings",
"my_settings": "My Settings" "my_settings": "My Settings",
"overview_metrics": "Overview Metrics",
"dbcall_metrics": "Database Calls",
"external_metrics": "External Calls"
} }

View File

@ -2,6 +2,7 @@
import { notification } from 'antd'; import { notification } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get'; import getLocalStorageApi from 'api/browser/localstorage/get';
import loginApi from 'api/user/login'; import loginApi from 'api/user/login';
import { Logout } from 'api/utils';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
@ -103,7 +104,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
history.push(ROUTES.UN_AUTHORIZED); history.push(ROUTES.UN_AUTHORIZED);
} }
} else { } else {
history.push(ROUTES.SOMETHING_WENT_WRONG); Logout();
notification.error({ notification.error({
message: response.error || t('something_went_wrong'), message: response.error || t('something_went_wrong'),

View File

@ -5,6 +5,7 @@ import history from 'lib/history';
import store from 'store'; import store from 'store';
import { import {
LOGGED_IN, LOGGED_IN,
UPDATE_ORG,
UPDATE_USER, UPDATE_USER,
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
UPDATE_USER_ORG_ROLE, UPDATE_USER_ORG_ROLE,
@ -51,5 +52,12 @@ export const Logout = (): void => {
}, },
}); });
store.dispatch({
type: UPDATE_ORG,
payload: {
org: [],
},
});
history.push(ROUTES.LOGIN); history.push(ROUTES.LOGIN);
}; };

View File

@ -27,12 +27,19 @@ function RouteTab({
onChange={onChange} onChange={onChange}
destroyInactiveTabPane destroyInactiveTabPane
activeKey={activeKey} activeKey={activeKey}
animated
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
{...rest} {...rest}
> >
{routes.map( {routes.map(
({ Component, name }): JSX.Element => ( ({ Component, name, route }): JSX.Element => (
<TabPane tab={name} key={name}> <TabPane
tabKey={route}
animated
destroyInactiveTabPane
tab={name}
key={name}
>
<Component /> <Component />
</TabPane> </TabPane>
), ),

View File

@ -3,4 +3,5 @@ export const ENVIRONMENT = {
process?.env?.FRONTEND_API_ENDPOINT || process?.env?.FRONTEND_API_ENDPOINT ||
process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') || process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') ||
'', '',
NODE_ENV: process?.env?.NODE_ENV,
}; };

View File

@ -18,7 +18,7 @@ const ROUTES = {
ALL_CHANNELS: '/settings/channels', ALL_CHANNELS: '/settings/channels',
CHANNELS_NEW: '/setting/channels/new', CHANNELS_NEW: '/setting/channels/new',
CHANNELS_EDIT: '/setting/channels/edit/:id', CHANNELS_EDIT: '/setting/channels/edit/:id',
ALL_ERROR: '/errors', ALL_ERROR: '/exceptions',
ERROR_DETAIL: '/error-detail', ERROR_DETAIL: '/error-detail',
VERSION: '/status', VERSION: '/status',
MY_SETTINGS: '/my-settings', MY_SETTINGS: '/my-settings',

View File

@ -1,4 +1,4 @@
import { notification, Table, Typography } from 'antd'; import { notification, Table, Tooltip, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import getAll from 'api/errors/getAll'; import getAll from 'api/errors/getAll';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
@ -47,11 +47,13 @@ function AllErrors(): JSX.Element {
dataIndex: 'exceptionType', dataIndex: 'exceptionType',
key: 'exceptionType', key: 'exceptionType',
render: (value, record): JSX.Element => ( render: (value, record): JSX.Element => (
<Link <Tooltip overlay={(): JSX.Element => value}>
to={`${ROUTES.ERROR_DETAIL}?serviceName=${record.serviceName}&errorType=${record.exceptionType}`} <Link
> to={`${ROUTES.ERROR_DETAIL}?serviceName=${record.serviceName}&errorType=${record.exceptionType}`}
{value} >
</Link> {value}
</Link>
</Tooltip>
), ),
sorter: (a, b): number => sorter: (a, b): number =>
a.exceptionType.charCodeAt(0) - b.exceptionType.charCodeAt(0), a.exceptionType.charCodeAt(0) - b.exceptionType.charCodeAt(0),
@ -61,13 +63,15 @@ function AllErrors(): JSX.Element {
dataIndex: 'exceptionMessage', dataIndex: 'exceptionMessage',
key: 'exceptionMessage', key: 'exceptionMessage',
render: (value): JSX.Element => ( render: (value): JSX.Element => (
<Typography.Paragraph <Tooltip overlay={(): JSX.Element => value}>
ellipsis={{ <Typography.Paragraph
rows: 2, ellipsis={{
}} rows: 2,
> }}
{value} >
</Typography.Paragraph> {value}
</Typography.Paragraph>
</Tooltip>
), ),
}, },
{ {

View File

@ -1,7 +1,5 @@
import { Button, Typography } from 'antd'; import { Button, Typography } from 'antd';
import getQueryResult from 'api/widgets/getQuery'; import getQueryResult from 'api/widgets/getQuery';
import { AxiosError } from 'axios';
import { ChartData } from 'chart.js';
import { GraphOnClickHandler } from 'components/Graph'; import { GraphOnClickHandler } from 'components/Graph';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import TimePreference from 'components/TimePreferenceDropDown'; import TimePreference from 'components/TimePreferenceDropDown';
@ -17,7 +15,8 @@ import GetMaxMinTime from 'lib/getMaxMinTime';
import GetMinMax from 'lib/getMinMax'; import GetMinMax from 'lib/getMinMax';
import getStartAndEndTime from 'lib/getStartAndEndTime'; import getStartAndEndTime from 'lib/getStartAndEndTime';
import getStep from 'lib/getStep'; 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 { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
@ -37,13 +36,6 @@ function FullView({
GlobalReducer GlobalReducer
>((state) => state.globalTime); >((state) => state.globalTime);
const [state, setState] = useState<FullViewState>({
error: false,
errorMessage: '',
loading: true,
payload: undefined,
});
const getSelectedTime = useCallback( const getSelectedTime = useCallback(
() => () =>
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')), timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
@ -55,107 +47,82 @@ function FullView({
enum: widget?.timePreferance || 'GLOBAL_TIME', enum: widget?.timePreferance || 'GLOBAL_TIME',
}); });
const onFetchDataHandler = useCallback(async () => { const maxMinTime = GetMaxMinTime({
try { graphType: widget.panelTypes,
const maxMinTime = GetMaxMinTime({ maxTime,
graphType: widget.panelTypes, minTime,
maxTime, });
minTime,
});
const getMinMax = ( const getMinMax = (
time: timePreferenceType, time: timePreferenceType,
): { min: string | number; max: string | number } => { ): { min: string | number; max: string | number } => {
if (time === 'GLOBAL_TIME') { if (time === 'GLOBAL_TIME') {
const minMax = GetMinMax(globalSelectedTime); const minMax = GetMinMax(globalSelectedTime);
return { return {
min: convertToNanoSecondsToSecond(minMax.minTime / 1000), min: convertToNanoSecondsToSecond(minMax.minTime / 1000),
max: convertToNanoSecondsToSecond(minMax.maxTime / 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 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(() => { const minMax = getStartAndEndTime({
onFetchDataHandler(); type: selectedTime.enum,
}, [onFetchDataHandler]); maxTime: maxMinTime.maxTime,
minTime: maxMinTime.minTime,
});
return { min: parseInt(minMax.start, 10), max: parseInt(minMax.end, 10) };
};
if (state.error && !state.loading) { const queryMinMax = getMinMax(selectedTime.enum);
return (
<NotFoundContainer> const queryLength = widget.query.filter((e) => e.query.length !== 0);
<Typography>{state.errorMessage}</Typography>
</NotFoundContainer> 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 ( return (
<div> <NotFoundContainer>
<Spinner height="80vh" size="large" tip="Loading..." /> <Typography>{errorMessage}</Typography>
</div> </NotFoundContainer>
); );
} }
@ -169,17 +136,27 @@ function FullView({
setSelectedTime, setSelectedTime,
}} }}
/> />
<Button onClick={onFetchDataHandler} type="primary"> <Button
onClick={(): void => {
response.forEach((e) => e.refetch());
}}
type="primary"
>
Refresh Refresh
</Button> </Button>
</TimeContainer> </TimeContainer>
)} )}
{/* <GraphContainer> */}
<GridGraphComponent <GridGraphComponent
{...{ {...{
GRAPH_TYPES: widget.panelTypes, 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, isStacked: widget.isStacked,
opacity: widget.opacity, opacity: widget.opacity,
title: widget.title, title: widget.title,
@ -188,18 +165,10 @@ function FullView({
yAxisUnit, yAxisUnit,
}} }}
/> />
{/* </GraphContainer> */}
</> </>
); );
} }
interface FullViewState {
loading: boolean;
error: boolean;
errorMessage: string;
payload: ChartData | undefined;
}
interface FullViewProps { interface FullViewProps {
widget: Widgets; widget: Widgets;
fullViewOptions?: boolean; fullViewOptions?: boolean;

View File

@ -65,7 +65,7 @@ function GridCardGraph({
.map(async (query) => { .map(async (query) => {
const result = await getQueryResult({ const result = await getQueryResult({
end, end,
query: query.query, query: encodeURIComponent(query.query),
start, start,
step: '60', step: '60',
}); });

View File

@ -28,6 +28,7 @@ function GridGraph(): JSX.Element {
const { dashboards, loading } = useSelector<AppState, DashboardReducer>( const { dashboards, loading } = useSelector<AppState, DashboardReducer>(
(state) => state.dashboards, (state) => state.dashboards,
); );
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
const [saveLayoutState, setSaveLayoutState] = useState<State>({ const [saveLayoutState, setSaveLayoutState] = useState<State>({
loading: false, loading: false,
error: false, error: false,
@ -251,8 +252,13 @@ function GridGraph(): JSX.Element {
const isQueryType = type === 'VALUE'; const isQueryType = type === 'VALUE';
return ( return (
<CardContainer key={rest.i} data-grid={rest}> <CardContainer
<Card isQueryType={isQueryType}> isQueryType={isQueryType}
isDarkMode={isDarkMode}
key={rest.i}
data-grid={rest}
>
<Card isDarkMode={isDarkMode} isQueryType={isQueryType}>
<Component /> <Component />
</Card> </Card>
</CardContainer> </CardContainer>

View File

@ -1,6 +1,7 @@
import { Button as ButtonComponent, Card as CardComponent } from 'antd'; import { Button as ButtonComponent, Card as CardComponent } from 'antd';
import { StyledCSS } from 'container/GantChart/Trace/styles';
import RGL, { WidthProvider } from 'react-grid-layout'; import RGL, { WidthProvider } from 'react-grid-layout';
import styled from 'styled-components'; import styled, { css } from 'styled-components';
const ReactGridLayoutComponent = WidthProvider(RGL); const ReactGridLayoutComponent = WidthProvider(RGL);
@ -18,20 +19,34 @@ export const Card = styled(CardComponent)<Props>`
} }
`; `;
export const CardContainer = styled.div` interface Props {
.react-resizable-handle { isDarkMode: boolean;
position: absolute; }
width: 20px;
height: 20px; export const CardContainer = styled.div<Props>`
bottom: 0; :hover {
right: 0; .react-resizable-handle {
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4='); position: absolute;
background-position: bottom right; width: 20px;
padding: 0 3px 3px 0; height: 20px;
background-repeat: no-repeat; bottom: 0;
background-origin: content-box; right: 0;
box-sizing: border-box; background-position: bottom right;
cursor: se-resize; 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}")`};
`;
}}
} }
`; `;

View File

@ -1,7 +0,0 @@
import React from 'react';
function IsRouteAccessible(): JSX.Element {
return <div>asd</div>;
}
export default IsRouteAccessible;

View File

@ -1,6 +1,7 @@
import { notification } from 'antd';
import getAll from 'api/alerts/getAll'; import getAll from 'api/alerts/getAll';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import React from 'react'; import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
@ -8,15 +9,37 @@ import ListAlert from './ListAlert';
function ListAlertRules(): JSX.Element { function ListAlertRules(): JSX.Element {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { data, isError, isLoading, refetch } = useQuery('allAlerts', { const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', {
queryFn: getAll, queryFn: getAll,
cacheTime: 0, 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) { if (isError) {
return <div>{data?.error || t('something_went_wrong')}</div>; 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) { if (isLoading || !data?.payload) {
return <Spinner height="75vh" tip="Loading Rules..." />; return <Spinner height="75vh" tip="Loading Rules..." />;
} }

View File

@ -77,6 +77,9 @@ function ImportJSON({
...queryData, ...queryData,
queryData: [], queryData: [],
})), })),
error: false,
errorMessage: '',
loading: false,
}, },
})), })),
}; };

View File

@ -16,7 +16,7 @@ import MetricReducer from 'types/reducer/metrics';
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles'; import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
import TopEndpointsTable from '../TopEndpointsTable'; import TopEndpointsTable from '../TopEndpointsTable';
import { Button, TableContainerCard } from './styles'; import { Button } from './styles';
function Application({ getWidget }: DashboardProps): JSX.Element { function Application({ getWidget }: DashboardProps): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>(); const { servicename } = useParams<{ servicename?: string }>();
@ -48,7 +48,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
); );
}; };
const onClickhandler = async ( const onClickHandler = async (
event: ChartEvent, event: ChartEvent,
elements: ActiveElement[], elements: ActiveElement[],
chart: Chart, chart: Chart,
@ -119,7 +119,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
<GraphContainer> <GraphContainer>
<Graph <Graph
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onClickhandler(ChartEvent, activeElements, chart, data, 'Application'); onClickHandler(ChartEvent, activeElements, chart, data, 'Application');
}} }}
name="application_latency" name="application_latency"
type="line" type="line"
@ -189,7 +189,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
name="request_per_sec" name="request_per_sec"
fullViewOptions={false} fullViewOptions={false}
onClickHandler={(event, element, chart, data): void => { onClickHandler={(event, element, chart, data): void => {
onClickhandler(event, element, chart, data, 'Request'); onClickHandler(event, element, chart, data, 'Request');
}} }}
widget={getWidget([ widget={getWidget([
{ {
@ -223,7 +223,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
name="error_percentage_%" name="error_percentage_%"
fullViewOptions={false} fullViewOptions={false}
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
onClickhandler(ChartEvent, activeElements, chart, data, 'Error'); onClickHandler(ChartEvent, activeElements, chart, data, 'Error');
}} }}
widget={getWidget([ widget={getWidget([
{ {
@ -238,9 +238,9 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
</Col> </Col>
<Col span={12}> <Col span={12}>
<TableContainerCard> <Card>
<TopEndpointsTable data={topEndPoints} /> <TopEndpointsTable data={topEndPoints} />
</TableContainerCard> </Card>
</Col> </Col>
</Row> </Row>
</> </>

View File

@ -1,8 +1,6 @@
import { Button as ButtonComponent } from 'antd'; import { Button as ButtonComponent } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
import { Card } from '../styles';
export const Button = styled(ButtonComponent)` export const Button = styled(ButtonComponent)`
&&& { &&& {
position: absolute; position: absolute;
@ -10,6 +8,3 @@ export const Button = styled(ButtonComponent)`
display: none; display: none;
} }
`; `;
export const TableContainerCard = styled(Card)`
overflow-x: auto;
`;

View File

@ -1,4 +1,4 @@
import { Button, Table, Tooltip } from 'antd'; import { Table, Tooltip, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query'; import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
@ -51,17 +51,12 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
title: 'Name', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
ellipsis: true,
// eslint-disable-next-line react/display-name
render: (text: string): JSX.Element => ( render: (text: string): JSX.Element => (
<Tooltip placement="topLeft" title={text}> <Tooltip placement="topLeft" title={text}>
<Button <Typography.Link onClick={(): void => handleOnClick(text)}>
className="topEndpointsButton"
type="link"
onClick={(): void => handleOnClick(text)}
>
{text} {text}
</Button> </Typography.Link>
</Tooltip> </Tooltip>
), ),
}, },
@ -101,9 +96,9 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element {
title={(): string => { title={(): string => {
return 'Top Endpoints'; return 'Top Endpoints';
}} }}
tableLayout="fixed"
dataSource={data} dataSource={data}
columns={columns} columns={columns}
pagination={false}
rowKey="name" rowKey="name"
/> />
); );

View File

@ -1,54 +1,108 @@
import { Tabs } from 'antd'; import RouteTab from 'components/RouteTab';
import ROUTES from 'constants/routes';
import React from 'react'; import React from 'react';
import { generatePath, useParams } from 'react-router-dom';
import { useLocation } from 'react-use';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import ResourceAttributesFilter from './ResourceAttributesFilter'; import ResourceAttributesFilter from './ResourceAttributesFilter';
import Application from './Tabs/Application';
import DBCall from './Tabs/DBCall'; import DBCall from './Tabs/DBCall';
import External from './Tabs/External'; 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 { function ServiceMetrics(): JSX.Element {
const getWidget = (query: Widgets['query']): Widgets => { const { search } = useLocation();
return { const { servicename } = useParams<{ servicename: string }>();
description: '',
id: '', const searchParams = new URLSearchParams(search);
isStacked: false, const tab = searchParams.get('tab');
nullZeroValues: '',
opacity: '0', const overMetrics = 'Overview Metrics';
panelTypes: 'TIME_SERIES', const dbCallMetrics = 'Database Calls';
query, const externalMetrics = 'External Calls';
queryData: {
data: [], const getActiveKey = (): string => {
error: false, if (tab === null) {
errorMessage: '', return overMetrics;
loading: false, }
},
timePreferance: 'GLOBAL_TIME', if (tab === dbCallMetrics) {
title: '', return dbCallMetrics;
stepSize: 60, }
};
if (tab === externalMetrics) {
return externalMetrics;
}
return overMetrics;
}; };
const activeKey = getActiveKey();
return ( return (
<> <>
<ResourceAttributesFilter /> <ResourceAttributesFilter />
<Tabs defaultActiveKey="1"> <RouteTab
<TabPane animated destroyInactiveTabPane tab="Application Metrics" key="1"> routes={[
<Application getWidget={getWidget} /> {
</TabPane> Component: OverViewTab,
name: overMetrics,
<TabPane animated destroyInactiveTabPane tab="External Calls" key="2"> route: `${generatePath(ROUTES.SERVICE_METRICS, {
<External getWidget={getWidget} /> servicename,
</TabPane> })}?tab=${overMetrics}`,
},
<TabPane animated destroyInactiveTabPane tab="Database Calls" key="3"> {
<DBCall getWidget={getWidget} /> Component: DbCallTab,
</TabPane> name: dbCallMetrics,
</Tabs> 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);

View File

@ -77,7 +77,6 @@ function Metrics(): JSX.Element {
loading={loading} loading={loading}
dataSource={services} dataSource={services}
columns={columns} columns={columns}
pagination={false}
rowKey="serviceName" rowKey="serviceName"
/> />
</Container> </Container>

View File

@ -12,9 +12,23 @@ function ShareModal({
onToggleHandler, onToggleHandler,
selectedData, selectedData,
}: ShareModalProps): JSX.Element { }: ShareModalProps): JSX.Element {
const [jsonValue, setJSONValue] = useState<string>( const getParsedValue = (): string => {
JSON.stringify(selectedData, null, 2), 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 [isViewJSON, setIsViewJSON] = useState<boolean>(false);
const { t } = useTranslation(['dashboard', 'common']); const { t } = useTranslation(['dashboard', 'common']);
const [state, setCopy] = useCopyToClipboard(); const [state, setCopy] = useCopyToClipboard();

View File

@ -5,6 +5,7 @@ import React, { memo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
import DashboardReducer from 'types/reducer/dashboards'; import DashboardReducer from 'types/reducer/dashboards';
import { NewWidgetProps } from '../../index'; import { NewWidgetProps } from '../../index';
@ -18,6 +19,7 @@ function WidgetGraph({
const { dashboards, isQueryFired } = useSelector<AppState, DashboardReducer>( const { dashboards, isQueryFired } = useSelector<AppState, DashboardReducer>(
(state) => state.dashboards, (state) => state.dashboards,
); );
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
const [selectedDashboard] = dashboards; const [selectedDashboard] = dashboards;
const { search } = useLocation(); const { search } = useLocation();
@ -31,7 +33,11 @@ function WidgetGraph({
const selectedWidget = widgets.find((e) => e.id === widgetId); const selectedWidget = widgets.find((e) => e.id === widgetId);
if (selectedWidget === undefined) { if (selectedWidget === undefined) {
return <Card isQueryType={false}>Invalid widget</Card>; return (
<Card isDarkMode={isDarkMode} isQueryType={false}>
Invalid widget
</Card>
);
} }
const { queryData } = selectedWidget; const { queryData } = selectedWidget;

View File

@ -38,7 +38,7 @@ function DisplayName({
dispatch({ dispatch({
type: UPDATE_ORG_NAME, type: UPDATE_ORG_NAME,
payload: { payload: {
index, orgId,
name: orgName, name: orgName,
}, },
}); });

View File

@ -1,5 +1,5 @@
import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons'; 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 getLocalStorageKey from 'api/browser/localstorage/get';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app'; import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
@ -19,6 +19,7 @@ import {
Sider, Sider,
SlackButton, SlackButton,
SlackMenuItemContainer, SlackMenuItemContainer,
Tags,
VersionContainer, VersionContainer,
} from './styles'; } from './styles';
@ -96,13 +97,21 @@ function SideNav(): JSX.Element {
selectedKeys={[pathname]} selectedKeys={[pathname]}
mode="inline" mode="inline"
> >
{menus.map(({ to, Icon, name }) => ( {menus.map(({ to, Icon, name, tags }) => (
<Menu.Item <Menu.Item
key={to} key={to}
icon={<Icon />} icon={<Icon />}
onClick={(): void => onClickHandler(to)} 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> </Menu.Item>
))} ))}
{sidebar.map((props, index) => ( {sidebar.map((props, index) => (

View File

@ -41,6 +41,7 @@ const menus: SidebarMenu[] = [
to: ROUTES.SERVICE_MAP, to: ROUTES.SERVICE_MAP,
name: 'Service Map', name: 'Service Map',
Icon: DeploymentUnitOutlined, Icon: DeploymentUnitOutlined,
tags: ['Beta'],
}, },
{ {
Icon: LineChartOutlined, Icon: LineChartOutlined,
@ -63,6 +64,7 @@ interface SidebarMenu {
to: string; to: string;
name: string; name: string;
Icon: typeof ApiOutlined; Icon: typeof ApiOutlined;
tags?: string[];
} }
export default menus; export default menus;

View File

@ -1,4 +1,4 @@
import { Layout, Typography } from 'antd'; import { Layout, Tag, Typography } from 'antd';
import { StyledCSS } from 'container/GantChart/Trace/styles'; import { StyledCSS } from 'container/GantChart/Trace/styles';
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
@ -75,3 +75,11 @@ export const VersionContainer = styled.div`
display: flex; display: flex;
} }
`; `;
export const Tags = styled(Tag)`
&&& {
position: absolute;
top: 0;
border-radius: 0.5rem;
}
`;

View File

@ -1,7 +1,7 @@
import { Checkbox, notification, Typography } from 'antd'; import { Checkbox, notification, Tooltip, Typography } from 'antd';
import getFilters from 'api/trace/getFilters'; import getFilters from 'api/trace/getFilters';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import React, { useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { getFilter, updateURL } from 'store/actions/trace/util'; 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 { GlobalReducer } from 'types/reducer/globalTime';
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace'; import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
import { CheckBoxContainer } from './styles'; import { CheckBoxContainer, ParaGraph } from './styles';
function CheckBoxComponent(props: CheckBoxProps): JSX.Element { function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
const { const {
@ -155,6 +155,11 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
const isCheckBoxSelected = isUserSelected; const isCheckBoxSelected = isUserSelected;
const TooTipOverLay = useMemo(
(): JSX.Element => <Typography>{keyValue}</Typography>,
[keyValue],
);
return ( return (
<CheckBoxContainer> <CheckBoxContainer>
<Checkbox <Checkbox
@ -164,7 +169,9 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element {
defaultChecked defaultChecked
key={keyValue} key={keyValue}
> >
{keyValue} <Tooltip overlay={TooTipOverLay}>
<ParaGraph ellipsis>{keyValue}</ParaGraph>
</Tooltip>
</Checkbox> </Checkbox>
{isCheckBoxSelected ? ( {isCheckBoxSelected ? (
<Typography>{value}</Typography> <Typography>{value}</Typography>

View File

@ -1,3 +1,4 @@
import { Typography } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
export const CheckBoxContainer = styled.div` export const CheckBoxContainer = styled.div`
@ -9,3 +10,10 @@ export const CheckBoxContainer = styled.div`
margin-top: 0.5rem; margin-top: 0.5rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
`; `;
export const ParaGraph = styled(Typography.Paragraph)`
&&& {
margin: 0;
max-width: 8rem;
}
`;

View File

@ -34,7 +34,7 @@ function TraceGraph(): JSX.Element {
); );
} }
if (loading || payload === undefined) { if (loading) {
return ( return (
<Container> <Container>
<Spinner height="20vh" size="small" tip="Loading..." /> <Spinner height="20vh" size="small" tip="Loading..." />

View File

@ -1,7 +1,9 @@
import { notification } from 'antd';
import getTriggeredApi from 'api/alerts/getTriggered'; import getTriggeredApi from 'api/alerts/getTriggered';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { State } from 'hooks/useFetch'; import { State } from 'hooks/useFetch';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PayloadProps } from 'types/api/alerts/getTriggered'; import { PayloadProps } from 'types/api/alerts/getTriggered';
import TriggerComponent from './TriggeredAlert'; import TriggerComponent from './TriggeredAlert';
@ -14,6 +16,7 @@ function TriggeredAlerts(): JSX.Element {
success: false, success: false,
payload: [], payload: [],
}); });
const { t } = useTranslation(['common']);
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
try { try {
@ -56,8 +59,16 @@ function TriggeredAlerts(): JSX.Element {
fetchData(); fetchData();
}, [fetchData]); }, [fetchData]);
useEffect(() => {
if (groupState.error) {
notification.error({
message: groupState.errorMessage || t('something_went_wrong'),
});
}
}, [groupState.error, groupState.errorMessage, t]);
if (groupState.error) { if (groupState.error) {
return <div>{groupState.errorMessage}</div>; return <TriggerComponent allAlerts={[]} />;
} }
if (groupState.loading || groupState.payload === undefined) { if (groupState.loading || groupState.payload === undefined) {

View File

@ -5,12 +5,13 @@ import { Card } from 'antd';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { ForceGraph2D } from 'react-force-graph'; 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 { RouteComponentProps, withRouter } from 'react-router-dom';
import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions'; import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import styled from 'styled-components'; import styled from 'styled-components';
import { GlobalTime } from 'types/actions/globalTime'; import { GlobalTime } from 'types/actions/globalTime';
import AppReducer from 'types/reducer/app';
import SelectService from './SelectService'; import SelectService from './SelectService';
import { getGraphData, getTooltip, getZoomPx, transformLabel } from './utils'; import { getGraphData, getTooltip, getZoomPx, transformLabel } from './utils';
@ -53,6 +54,8 @@ export interface graphDataType {
function ServiceMap(props: ServiceMapProps): JSX.Element { function ServiceMap(props: ServiceMapProps): JSX.Element {
const fgRef = useRef(); const fgRef = useRef();
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
const { getDetailedServiceMapItems, globalTime, serviceMap } = props; const { getDetailedServiceMapItems, globalTime, serviceMap } = props;
useEffect(() => { useEffect(() => {
@ -115,10 +118,11 @@ function ServiceMap(props: ServiceMapProps): JSX.Element {
ctx.fillStyle = node.color; ctx.fillStyle = node.color;
ctx.beginPath(); ctx.beginPath();
ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false); ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false);
ctx.fillStyle = isDarkMode ? '#3d0b00' : '#ffbcad';
ctx.fill(); ctx.fill();
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.fillStyle = '#646464'; ctx.fillStyle = isDarkMode ? '#ffffff' : '#000000';
ctx.fillText(label, node.x, node.y); ctx.fillText(label, node.x, node.y);
}} }}
onNodeClick={(node) => { onNodeClick={(node) => {

View File

@ -5,6 +5,7 @@ import Spinner from 'components/Spinner';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import ErrorDetailsContainer from 'container/ErrorDetails'; import ErrorDetailsContainer from 'container/ErrorDetails';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { Redirect, useLocation } from 'react-router-dom'; import { Redirect, useLocation } from 'react-router-dom';
@ -13,6 +14,7 @@ import { PayloadProps } from 'types/api/errors/getById';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
function ErrorDetails(): JSX.Element { function ErrorDetails(): JSX.Element {
const { t } = useTranslation(['common']);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
@ -22,6 +24,7 @@ function ErrorDetails(): JSX.Element {
const errorId = params.get('errorId'); const errorId = params.get('errorId');
const errorType = params.get('errorType'); const errorType = params.get('errorType');
const serviceName = params.get('serviceName'); const serviceName = params.get('serviceName');
const defaultError = t('something_went_wrong');
const { data, status } = useQuery( 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) { if (errorType === null || serviceName === null) {
return <Redirect to={ROUTES.ALL_ERROR} />; return <Redirect to={ROUTES.ALL_ERROR} />;
} }
// when the api is in loading state
if (status === 'loading' || ErrorIdStatus === 'loading') { if (status === 'loading' || ErrorIdStatus === 'loading') {
return <Spinner tip="Loading.." />; return <Spinner tip="Loading.." />;
} }
// if any error occurred while loading
if (status === 'error' || ErrorIdStatus === 'error') { 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 ( return (

View File

@ -20,7 +20,10 @@ function Login(): JSX.Element {
enabled: !isLoggedIn, enabled: !isLoggedIn,
}); });
if (versionResult.status === 'error') { if (
versionResult.status === 'error' ||
(versionResult.status === 'success' && versionResult?.data.statusCode !== 200)
) {
return ( return (
<Typography> <Typography>
{versionResult.data?.error || t('something_went_wrong')} {versionResult.data?.error || t('something_went_wrong')}

View File

@ -1,3 +1,4 @@
import { notification } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get'; import getLocalStorageKey from 'api/browser/localstorage/get';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { SKIP_ONBOARDING } from 'constants/onboarding'; import { SKIP_ONBOARDING } from 'constants/onboarding';
@ -20,10 +21,20 @@ function Metrics({ getService }: MetricsProps): JSX.Element {
AppState, AppState,
GlobalReducer GlobalReducer
>((state) => state.globalTime); >((state) => state.globalTime);
const { services, resourceAttributeQueries } = useSelector< const {
AppState, services,
MetricReducer resourceAttributeQueries,
>((state) => state.metrics); error,
errorMessage,
} = useSelector<AppState, MetricReducer>((state) => state.metrics);
useEffect(() => {
if (error) {
notification.error({
message: errorMessage,
});
}
}, [error, errorMessage]);
const selectedTags = useMemo( const selectedTags = useMemo(
() => () =>

View File

@ -10,8 +10,8 @@ export const Container = styled.div`
export const LeftContainer = styled(Card)` export const LeftContainer = styled(Card)`
flex: 0.5; flex: 0.5;
width: 95%; margin-right: 0.5rem;
padding-right: 0.5rem; width: 15rem;
.ant-card-body { .ant-card-body {
padding: 0; padding: 0;

View File

@ -7,6 +7,7 @@ import AppActions from 'types/actions';
import { import {
UPDATE_ALL_FILTERS, UPDATE_ALL_FILTERS,
UPDATE_TRACE_FILTER_LOADING, UPDATE_TRACE_FILTER_LOADING,
UPDATE_TRACE_GRAPH_LOADING,
} from 'types/actions/trace'; } from 'types/actions/trace';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace'; import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
@ -183,6 +184,12 @@ export const GetInitialTraceFilter = (
filterLoading: false, filterLoading: false,
}, },
}); });
dispatch({
type: UPDATE_TRACE_GRAPH_LOADING,
payload: {
loading: false,
},
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
dispatch({ dispatch({
@ -191,6 +198,12 @@ export const GetInitialTraceFilter = (
filterLoading: false, filterLoading: false,
}, },
}); });
dispatch({
type: UPDATE_TRACE_GRAPH_LOADING,
payload: {
loading: false,
},
});
} }
}; };
}; };

View File

@ -1,5 +1,6 @@
import { applyMiddleware, compose, createStore } from 'redux'; import { applyMiddleware, compose, createStore } from 'redux';
import thunk, { ThunkMiddleware } from 'redux-thunk'; import thunk, { ThunkMiddleware } from 'redux-thunk';
import AppActions from 'types/actions';
import reducers, { AppState } from './reducers'; import reducers, { AppState } from './reducers';
@ -8,8 +9,9 @@ const composeEnhancers =
const store = createStore( const store = createStore(
reducers, reducers,
// @TODO Add Type for AppActions also composeEnhancers(
composeEnhancers(applyMiddleware(thunk as ThunkMiddleware<AppState>)), applyMiddleware(thunk as ThunkMiddleware<AppState, AppActions>),
),
); );
export default store; export default store;

View File

@ -12,6 +12,7 @@ import {
UPDATE_CURRENT_VERSION, UPDATE_CURRENT_VERSION,
UPDATE_LATEST_VERSION, UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR, UPDATE_LATEST_VERSION_ERROR,
UPDATE_ORG,
UPDATE_ORG_NAME, UPDATE_ORG_NAME,
UPDATE_USER, UPDATE_USER,
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
@ -172,16 +173,19 @@ const appReducer = (
case UPDATE_ORG_NAME: { case UPDATE_ORG_NAME: {
const stateOrg = state.org || ({} as OrgPayload); const stateOrg = state.org || ({} as OrgPayload);
const { index, name: updatedName } = action.payload; const { orgId, name: updatedName } = action.payload;
const current = stateOrg[index];
const orgIndex = stateOrg.findIndex((e) => e.id === orgId);
const current = stateOrg[orgIndex];
const updatedOrg: OrgPayload = [ const updatedOrg: OrgPayload = [
...stateOrg.slice(0, index), ...stateOrg.slice(0, orgIndex),
{ {
...current, ...current,
name: updatedName, name: updatedName,
}, },
...stateOrg.slice(index + 1, stateOrg.length), ...stateOrg.slice(orgIndex + 1, stateOrg.length),
]; ];
return { return {
@ -190,6 +194,13 @@ const appReducer = (
}; };
} }
case UPDATE_ORG: {
return {
...state,
org: action.payload.org,
};
}
default: default:
return state; return state;
} }

View File

@ -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_ORG_ROLE = 'UPDATE_USER_ORG_ROLE';
export const UPDATE_USER = 'UPDATE_USER'; export const UPDATE_USER = 'UPDATE_USER';
export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME'; export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
export const UPDATE_ORG = 'UPDATE_ORG';
export interface SwitchDarkMode { export interface SwitchDarkMode {
type: typeof SWITCH_DARK_MODE; type: typeof SWITCH_DARK_MODE;
@ -98,7 +99,14 @@ export interface UpdateOrgName {
type: typeof UPDATE_ORG_NAME; type: typeof UPDATE_ORG_NAME;
payload: { payload: {
name: string; 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 | UpdateUserIsFetched
| UpdateUserOrgRole | UpdateUserOrgRole
| UpdateUser | UpdateUser
| UpdateOrgName; | UpdateOrgName
| UpdateOrg;

View File

@ -3,6 +3,7 @@ declare global {
namespace NodeJS { namespace NodeJS {
interface ProcessEnv { interface ProcessEnv {
FRONTEND_API_ENDPOINT: string | undefined; FRONTEND_API_ENDPOINT: string | undefined;
NODE_ENV: 'development' | 'production' | 'test';
} }
} }
} }

View File

@ -19,7 +19,8 @@
"noEmit": true, "noEmit": true,
"baseUrl": "./src", "baseUrl": "./src",
"downlevelIteration": true, "downlevelIteration": true,
"plugins": [{ "name": "typescript-plugin-css-modules" }] "plugins": [{ "name": "typescript-plugin-css-modules" }],
"types": ["cypress", "@testing-library/cypress", "node"]
}, },
"exclude": ["node_modules"], "exclude": ["node_modules"],
"include": [ "include": [
@ -31,6 +32,9 @@
"./conf/default.conf", "./conf/default.conf",
"./public", "./public",
"./tests", "./tests",
"playwright.config.ts" "playwright.config.ts",
"./commitlint.config.js",
"./webpack.config.js",
"./webpack.config.prod.js"
] ]
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// shared config (dev and prod) // shared config (dev and prod)
const { resolve } = require('path'); const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
@ -5,8 +6,7 @@ const portFinderSync = require('portfinder-sync');
const dotenv = require('dotenv'); const dotenv = require('dotenv');
const webpack = require('webpack'); const webpack = require('webpack');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
.BundleAnalyzerPlugin;
dotenv.config(); dotenv.config();
@ -106,7 +106,7 @@ const config = {
}, },
], ],
}, },
plugins: plugins, plugins,
performance: { performance: {
hints: false, hints: false,
}, },

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
// shared config (dev and prod) // shared config (dev and prod)
const { resolve } = require('path'); const { resolve } = require('path');
@ -9,8 +10,7 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
.BundleAnalyzerPlugin;
const Critters = require('critters-webpack-plugin'); const Critters = require('critters-webpack-plugin');
const plugins = [ const plugins = [
@ -119,13 +119,13 @@ const config = {
}, },
], ],
}, },
plugins: plugins, plugins,
optimization: { optimization: {
chunkIds: 'named', chunkIds: 'named',
concatenateModules: false, concatenateModules: false,
emitOnErrors: true, emitOnErrors: true,
flagIncludedChunks: 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, mangleWasmImports: true,
mergeDuplicateChunks: true, mergeDuplicateChunks: true,
minimize: true, minimize: true,

File diff suppressed because it is too large Load Diff

View File

@ -2333,6 +2333,40 @@ func (r *ClickHouseReader) setColdStorage(ctx context.Context, tableName string,
return nil 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. // GetDisks returns a list of disks {name, type} configured in clickhouse DB.
func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *model.ApiError) { func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *model.ApiError) {
diskItems := []model.DiskItem{} diskItems := []model.DiskItem{}

View File

@ -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/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", AdminAccess(aH.setTTL)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/settings/ttl", ViewAccess(aH.getTTL)).Methods(http.MethodGet) 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) 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) 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) { func (aH *APIHandler) getDisks(w http.ResponseWriter, r *http.Request) {
result, apiErr := (*aH.reader).GetDisks(context.Background()) result, apiErr := (*aH.reader).GetDisks(context.Background())
if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) {

View File

@ -50,6 +50,7 @@ type Reader interface {
// Setter Interfaces // Setter Interfaces
SetTTL(ctx context.Context, ttlParams *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError) 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) GetMetricAutocompleteMetricNames(ctx context.Context, matchText string) (*[]string, *model.ApiError)
GetMetricAutocompleteTagKey(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError) GetMetricAutocompleteTagKey(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError)

View File

@ -545,7 +545,7 @@ func parseTTLParams(r *http.Request) (*model.TTLParams, error) {
// Validate the TTL duration. // Validate the TTL duration.
durationParsed, err := time.ParseDuration(delDuration) 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) 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 some cold storage is provided, validate the cold storage move TTL.
if len(coldStorage) > 0 { if len(coldStorage) > 0 {
toColdParsed, err = time.ParseDuration(toColdDuration) 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) return nil, fmt.Errorf("Not a valid toCold TTL duration %v", toColdDuration)
} }
if toColdParsed.Seconds() != 0 && toColdParsed.Seconds() >= durationParsed.Seconds() { 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 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) { func parseUserRequest(r *http.Request) (*model.User, error) {
var req model.User var req model.User
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
_ "net/http/pprof" // http profiler
"os" "os"
"time" "time"
@ -257,7 +258,15 @@ func (s *Server) Start() error {
zap.S().Error("Could not start HTTP server", zap.Error(err)) zap.S().Error("Could not start HTTP server", zap.Error(err))
} }
s.unavailableChannel <- healthcheck.Unavailable 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 return nil

View File

@ -6,7 +6,8 @@ import (
) )
const ( 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 var DEFAULT_TELEMETRY_ANONYMOUS = false

View File

@ -206,3 +206,8 @@ type GetErrorParams struct {
ErrorID string ErrorID string
ServiceName string ServiceName string
} }
type RemoveTTLParams struct {
Type string
RemoveAllTTL bool
}

View File

@ -246,6 +246,10 @@ type SetTTLResponseItem struct {
Message string `json:"message"` Message string `json:"message"`
} }
type RemoveTTLResponseItem struct {
Message string `json:"message"`
}
type DiskItem struct { type DiskItem struct {
Name string `json:"name,omitempty" ch:"name"` Name string `json:"name,omitempty" ch:"name"`
Type string `json:"type,omitempty" ch:"type"` Type string `json:"type,omitempty" ch:"type"`