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)
- [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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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
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 package.json ./
# configure node_env as production
ENV NODE_ENV=production
# Install the dependencies and make the folder
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: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",

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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'),

View File

@ -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);
};

View File

@ -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>
),

View File

@ -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,
};

View File

@ -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',

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 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>
),
},
{

View File

@ -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;

View File

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

View File

@ -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>

View File

@ -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}")`};
`;
}}
}
`;

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 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..." />;
}

View File

@ -77,6 +77,9 @@ function ImportJSON({
...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 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>
</>

View File

@ -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;
`;

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 { 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"
/>
);

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 { 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);

View File

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

View File

@ -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();

View File

@ -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;

View File

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

View File

@ -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) => (

View File

@ -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;

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 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;
}
`;

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 { 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>

View File

@ -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;
}
`;

View File

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

View File

@ -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) {

View File

@ -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) => {

View File

@ -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 (

View File

@ -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')}

View File

@ -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(
() =>

View File

@ -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;

View File

@ -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,
},
});
}
};
};

View File

@ -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;

View File

@ -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;
}

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 = '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;

View File

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

View File

@ -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"
]
}

View File

@ -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,
},

View File

@ -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,

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
}
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{}

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/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) {

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

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

View File

@ -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"`