fix: merge conflict removed

This commit is contained in:
Palash gupta 2022-05-19 21:29:21 +05:30
commit f084637f84
No known key found for this signature in database
GPG Key ID: 8FD05AE6F9150AD6
60 changed files with 2747 additions and 1886 deletions

View File

@ -106,28 +106,39 @@ 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

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

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

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
import {
getDefaultOption,
getOptions,
} from 'container/Header/DateTimeSelection/config';
// import { AppState } from 'store/reducers';
} from 'container/TopNav/DateTimeSelection/config';
const CheckRouteDefaultGlobalTimeOptions = ({
route,

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
/// <reference types="cypress" />
import ROUTES from 'constants/routes';

View File

@ -26,7 +26,7 @@ describe('Global Time Metrics Application', () => {
cy.wait('@defaultApps');
//clicking on frontend
// clicking on frontend
cy.get('tr:nth-child(1) > td:first-child').click();
cy
@ -64,7 +64,7 @@ describe('Global Time Metrics Application', () => {
cy.wait('@topEndPoints');
cy.wait('@serviceOverview');
//TODO add errorPercentage also
// TODO add errorPercentage also
// cy.wait('@errorPercentage');
cy.wait('@requestPerSecond');

View File

@ -58,6 +58,7 @@ describe('Metrics', () => {
parseFloat(errorRate.toString()).toFixed(2),
);
expect(rpsName).to.be.equals(callRate.toString());
return null;
});
});
});

View File

@ -16,7 +16,7 @@ describe('Alerts', () => {
})
.as('defaultRules');
cy.visit(Cypress.env('baseUrl') + `${ROUTES.LIST_ALL_ALERT}`);
cy.visit(`${Cypress.env('baseUrl')}${ROUTES.LIST_ALL_ALERT}`);
cy.wait('@defaultRules');
});
@ -97,7 +97,7 @@ describe('Alerts', () => {
const defaultLabels = defaultRules.data.rules[index].labels;
expect(label).to.be.equals(defaultLabels['severity']);
expect(label).to.be.equals(defaultLabels.severity);
});
});
});

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable sonarjs/no-duplicate-string */
import ROUTES from 'constants/routes';
import { AppState } from 'store/reducers';
@ -33,7 +34,7 @@ describe('Trace', () => {
})
.as('Filters');
cy.visit(Cypress.env('baseUrl') + `${ROUTES.TRACE}`);
cy.visit(`${Cypress.env('baseUrl')}${ROUTES.TRACE}`);
});
it('First Initial Load should go with 3 AJAX request', () => {

View File

@ -4,9 +4,6 @@
"lib": ["es5", "dom"],
"compilerOptions": {
"noEmit": true,
// be explicit about types included
// to avoid clashing with Jest types
"types": ["cypress", "@testing-library/cypress", "node"],
"isolatedModules": false
},
"include": ["../node_modules/cypress", "./**/*.ts"]

View File

@ -15,7 +15,8 @@
"jest:coverage": "jest --coverage",
"jest:watch": "jest --watch",
"postinstall": "yarn husky:configure",
"husky:configure": "cd .. && husky install frontend/.husky"
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
"commitlint": "commitlint --edit $1"
},
"engines": {
"node": ">=12.13.0"
@ -109,6 +110,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",
"@testing-library/cypress": "^8.0.0",
"@testing-library/react-hooks": "^7.0.2",

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

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

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

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

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

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": [
@ -30,6 +31,9 @@
"./__mocks__",
"./conf/default.conf",
"./public",
"./cypress"
"./cypress",
"./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"`