mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 22:39:01 +08:00
UX updates
This commit is contained in:
parent
325ca434d4
commit
9ac2dece11
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@ -46,16 +46,15 @@
|
||||
}
|
||||
},
|
||||
"@ant-design/icons": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.5.0.tgz",
|
||||
"integrity": "sha512-ZAKJcmr4DBV3NWr8wm2dCxNKN4eFrX+qCaPsuFejP6FRsf+m5OKxvCVi9bSp1lmKWeOI5yECAx5s0uFm4QHuPw==",
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.6.2.tgz",
|
||||
"integrity": "sha512-QsBG2BxBYU/rxr2eb8b2cZ4rPKAPBpzAR+0v6rrZLp/lnyvflLH3tw1vregK+M7aJauGWjIGNdFmUfpAOtw25A==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons-svg": "^4.0.0",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"classnames": "^2.2.6",
|
||||
"insert-css": "^2.0.0",
|
||||
"rc-util": "^5.0.1"
|
||||
"rc-util": "^5.9.4"
|
||||
}
|
||||
},
|
||||
"@ant-design/icons-svg": {
|
||||
@ -11076,11 +11075,6 @@
|
||||
"css-in-js-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"insert-css": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/insert-css/-/insert-css-2.0.0.tgz",
|
||||
"integrity": "sha1-610Ql7dUL0x56jBg067gfQU4gPQ="
|
||||
},
|
||||
"internal-ip": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
|
||||
|
@ -12,6 +12,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@auth0/auth0-react": "^1.2.0",
|
||||
"@babel/core": "7.12.3",
|
||||
"@material-ui/core": "^4.0.0",
|
||||
|
@ -1,8 +1,27 @@
|
||||
import React, { useState } from "react";
|
||||
import { servicesItem } from "Src/store/actions";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Select } from "antd";
|
||||
import styled from "styled-components";
|
||||
const { Option } = Select;
|
||||
|
||||
const Container = styled.div`
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
.info {
|
||||
display:flex;
|
||||
font-family: Roboto;
|
||||
margin-left: auto;
|
||||
margin-right: 12px;
|
||||
color: #4F4F4F;
|
||||
font-size: 14px;
|
||||
.anticon-info-circle {
|
||||
margin-top: 22px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface SelectServiceProps {
|
||||
services: servicesItem[];
|
||||
zoomToService: (arg0: string) => void;
|
||||
@ -16,18 +35,28 @@ const SelectService = (props: SelectServiceProps) => {
|
||||
zoomToService(value);
|
||||
};
|
||||
return (
|
||||
<Select
|
||||
style={{ width: 270, marginBottom: "56px" }}
|
||||
placeholder="Select a service"
|
||||
onChange={handleSelect}
|
||||
value={selectedVal}
|
||||
>
|
||||
{services.map(({ serviceName }) => (
|
||||
<Option key={serviceName} value={serviceName}>
|
||||
{serviceName}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<Container>
|
||||
<Select
|
||||
style={{ width: 270, marginBottom: "56px" }}
|
||||
placeholder="Select a service"
|
||||
onChange={handleSelect}
|
||||
value={selectedVal}
|
||||
>
|
||||
{services.map(({ serviceName }) => (
|
||||
<Option key={serviceName} value={serviceName}>
|
||||
{serviceName}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<div className='info'>
|
||||
<InfoCircleOutlined />
|
||||
<div>
|
||||
|
||||
<div>-> Size of circles is proportial to the number of requests served by each node </div>
|
||||
<div>-> Click on node name to reposition the node</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -8,11 +8,28 @@ import {
|
||||
getDetailedServiceMapItems,
|
||||
} from "Src/store/actions";
|
||||
import { Spin } from "antd";
|
||||
|
||||
import styled from "styled-components";
|
||||
import { StoreState } from "../../store/reducers";
|
||||
import { getGraphData } from "./utils";
|
||||
import SelectService from "./SelectService";
|
||||
import { ForceGraph2D } from "react-force-graph";
|
||||
|
||||
const Container = styled.div`
|
||||
.force-graph-container .graph-tooltip {
|
||||
background: black;
|
||||
padding: 1px;
|
||||
.keyval {
|
||||
display: flex;
|
||||
.key {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.val {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ServiceMapProps extends RouteComponentProps<any> {
|
||||
serviceMap: serviceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
@ -52,13 +69,13 @@ const ServiceMap = (props: ServiceMapProps) => {
|
||||
}
|
||||
|
||||
const zoomToService = (value: string) => {
|
||||
fgRef && fgRef.current.zoomToFit(700, 480, (e) => e.id === value);
|
||||
fgRef && fgRef.current.zoomToFit(700, 280, (e) => e.id === value);
|
||||
};
|
||||
|
||||
const { nodes, links } = getGraphData(serviceMap);
|
||||
const graphData = { nodes, links };
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<SelectService
|
||||
services={serviceMap.services}
|
||||
zoomToService={zoomToService}
|
||||
@ -66,7 +83,7 @@ const ServiceMap = (props: ServiceMapProps) => {
|
||||
<ForceGraph2D
|
||||
ref={fgRef}
|
||||
cooldownTicks={100}
|
||||
onEngineStop={() => fgRef.current.zoomToFit(100, 200)}
|
||||
onEngineStop={() => fgRef.current.zoomToFit(100, 120)}
|
||||
graphData={graphData}
|
||||
nodeLabel="id"
|
||||
linkAutoColorBy={(d) => d.target}
|
||||
@ -75,46 +92,46 @@ const ServiceMap = (props: ServiceMapProps) => {
|
||||
nodeCanvasObject={(node, ctx, globalScale) => {
|
||||
const label = node.id;
|
||||
const fontSize = node.fontSize;
|
||||
ctx.font = `${fontSize}px Sans-Serif`;
|
||||
const textWidth = ctx.measureText(label).width;
|
||||
const width = textWidth > node.width ? textWidth : node.width;
|
||||
const bckgDimensions = [width, node.height].map((n) => n + fontSize); // some padding
|
||||
ctx.font = `${fontSize}px Roboto`;
|
||||
const width = node.width;
|
||||
|
||||
ctx.fillStyle = node.color;
|
||||
ctx.fillRect(
|
||||
node.x - bckgDimensions[0] / 2,
|
||||
node.y - bckgDimensions[1] / 2,
|
||||
...bckgDimensions,
|
||||
);
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, width, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillStyle = "#333333";
|
||||
ctx.fillText(label, node.x, node.y);
|
||||
|
||||
node.__bckgDimensions = bckgDimensions; // to re-use in nodePointerAreaPaint
|
||||
}}
|
||||
onNodeClick={(node) => {
|
||||
const tooltip = document.querySelector(".graph-tooltip");
|
||||
if (tooltip && node) {
|
||||
tooltip.innerHTML = `<div style="padding:12px;background: black;border: 1px solid #BDBDBD;border-radius: 2px;">
|
||||
<div style="color:white; font-weight:bold; margin-bottom:8px;">${node.id}</div>
|
||||
<div style="color:white">P99 latency: ${node.p99 / 1000000}</div>
|
||||
<div style="color:white">Error Rate: ${node.errorRate}%</div>
|
||||
<div style="color:white">Request Per Sec: ${node.callRate}</div>
|
||||
tooltip.innerHTML = `<div style="color:#333333;padding:12px;background: white;border-radius: 2px;">
|
||||
<div style="font-weight:bold; margin-bottom:16px;">${node.id}</div>
|
||||
<div class="keyval">
|
||||
<div class="key">P99 latency:</div>
|
||||
<div class="val">${node.p99 / 1000000}</div>
|
||||
</div>
|
||||
<div class="keyval">
|
||||
<div class="key">Request:</div>
|
||||
<div class="val">${node.callRate}/sec</div>
|
||||
</div>
|
||||
<div class="keyval">
|
||||
<div class="key">Error Rate:</div>
|
||||
<div class="val">${node.errorRate}%</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}}
|
||||
nodePointerAreaPaint={(node, color, ctx) => {
|
||||
ctx.fillStyle = color;
|
||||
const bckgDimensions = node.__bckgDimensions;
|
||||
bckgDimensions &&
|
||||
ctx.fillRect(
|
||||
node.x - bckgDimensions[0] / 2,
|
||||
node.y - bckgDimensions[1] / 2,
|
||||
...bckgDimensions,
|
||||
);
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -2,20 +2,16 @@ import { uniqBy, uniq, maxBy, cloneDeep, find } from "lodash";
|
||||
import { serviceMapStore } from "Src/store/actions";
|
||||
import { graphDataType } from "./ServiceMap";
|
||||
|
||||
const MIN_WIDTH = 25;
|
||||
const MAX_WIDTH = 50;
|
||||
const MIN_HEIGHT = 10;
|
||||
const MAX_HEIGHT = 15;
|
||||
const DEFAULT_FONT_SIZE = 4;
|
||||
const MIN_WIDTH = 12;
|
||||
const MAX_WIDTH = 20;
|
||||
const DEFAULT_FONT_SIZE = 6;
|
||||
export const getDimensions = (num, highest) => {
|
||||
const percentage = (num / highest) * 100;
|
||||
const width = (percentage * (MAX_WIDTH - MIN_WIDTH)) / 100 + MIN_WIDTH;
|
||||
const height = (percentage * (MAX_HEIGHT - MIN_HEIGHT)) / 100 + MIN_HEIGHT;
|
||||
const fontSize = DEFAULT_FONT_SIZE;
|
||||
return {
|
||||
fontSize,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
};
|
||||
|
||||
@ -40,14 +36,13 @@ export const getGraphData = (serviceMap: serviceMapStore): graphDataType => {
|
||||
const uniqNodes = uniq([...uniqParent, ...uniqChild]);
|
||||
const nodes = uniqNodes.map((node, i) => {
|
||||
const service = find(services, (service) => service.serviceName === node);
|
||||
let color = "#84ff00";
|
||||
let color = "#88CEA5";
|
||||
if (!service) {
|
||||
return {
|
||||
id: node,
|
||||
group: i + 1,
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
width: MIN_WIDTH,
|
||||
height: MIN_HEIGHT,
|
||||
color,
|
||||
nodeVal: MIN_WIDTH,
|
||||
callRate: 0,
|
||||
@ -56,17 +51,16 @@ export const getGraphData = (serviceMap: serviceMapStore): graphDataType => {
|
||||
};
|
||||
}
|
||||
if (service.errorRate > 0) {
|
||||
color = "#f00a0a";
|
||||
color = "#F98989";
|
||||
} else if (service.fourXXRate > 0) {
|
||||
color = "#ebeb15";
|
||||
color = "#F9DA7B";
|
||||
}
|
||||
const { fontSize, width, height } = getDimensions(service.callRate, highestCallRate);
|
||||
const { fontSize, width } = getDimensions(service.callRate, highestCallRate);
|
||||
return {
|
||||
id: node,
|
||||
group: i + 1,
|
||||
fontSize,
|
||||
width,
|
||||
height,
|
||||
color,
|
||||
nodeVal: width,
|
||||
callRate: service.callRate,
|
||||
|
@ -44,36 +44,36 @@ export const getServiceMapItems = (globalTime: GlobalTime) => {
|
||||
"&end=" +
|
||||
globalTime.maxTime;
|
||||
|
||||
const response = await api.get<servicesMapItem[]>(apiV1 + request_string);
|
||||
// const response = {
|
||||
// data: [
|
||||
// {
|
||||
// parent: "driver",
|
||||
// child: "redis",
|
||||
// callCount: 17050,
|
||||
// },
|
||||
// {
|
||||
// parent: "frontend",
|
||||
// child: "driver",
|
||||
// callCount: 1263,
|
||||
// },
|
||||
// {
|
||||
// parent: "customer",
|
||||
// child: "mysql",
|
||||
// callCount: 1262,
|
||||
// },
|
||||
// {
|
||||
// parent: "frontend",
|
||||
// child: "customer",
|
||||
// callCount: 1262,
|
||||
// },
|
||||
// {
|
||||
// parent: "frontend",
|
||||
// child: "route",
|
||||
// callCount: 12636,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// const response = await api.get<servicesMapItem[]>(apiV1 + request_string);
|
||||
const response = {
|
||||
data: [
|
||||
{
|
||||
parent: "driver",
|
||||
child: "redisredisredisredisredisredisredisredisre",
|
||||
callCount: 17050,
|
||||
},
|
||||
{
|
||||
parent: "frontend",
|
||||
child: "driver",
|
||||
callCount: 1263,
|
||||
},
|
||||
{
|
||||
parent: "customer",
|
||||
child: "mysql",
|
||||
callCount: 1262,
|
||||
},
|
||||
{
|
||||
parent: "frontend",
|
||||
child: "customer",
|
||||
callCount: 1262,
|
||||
},
|
||||
{
|
||||
parent: "frontend",
|
||||
child: "route",
|
||||
callCount: 12636,
|
||||
},
|
||||
],
|
||||
};
|
||||
dispatch<serviceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: response.data,
|
||||
@ -86,77 +86,77 @@ export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
|
||||
let request_string =
|
||||
"/services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime;
|
||||
|
||||
const response = await api.get<servicesItem[]>(apiV1 + request_string);
|
||||
// const response = {
|
||||
// data: [
|
||||
// {
|
||||
// serviceName: "redis",
|
||||
// p99: 1179518000,
|
||||
// avgDuration: 742158850,
|
||||
// numCalls: 1898,
|
||||
// callRate: 0.019097411,
|
||||
// numErrors: 0,
|
||||
// errorRate: 3,
|
||||
// num4XX: 0,
|
||||
// fourXXRate: 0,
|
||||
// },
|
||||
// {
|
||||
// serviceName: "mysql",
|
||||
// p99: 1179518000,
|
||||
// avgDuration: 742158850,
|
||||
// numCalls: 1898,
|
||||
// callRate: 0.019097411,
|
||||
// numErrors: 0,
|
||||
// errorRate: 0,
|
||||
// num4XX: 0,
|
||||
// fourXXRate: 0,
|
||||
// },
|
||||
// {
|
||||
// serviceName: "frontend",
|
||||
// p99: 1179518000,
|
||||
// avgDuration: 742158850,
|
||||
// numCalls: 1898,
|
||||
// callRate: 0.000019097411,
|
||||
// numErrors: 0,
|
||||
// errorRate: 0,
|
||||
// num4XX: 0,
|
||||
// fourXXRate: 0,
|
||||
// },
|
||||
// {
|
||||
// serviceName: "customer",
|
||||
// p99: 728385000,
|
||||
// avgDuration: 342475680,
|
||||
// numCalls: 1897,
|
||||
// callRate: 0.000019087349,
|
||||
// numErrors: 0,
|
||||
// errorRate: 0,
|
||||
// num4XX: 0,
|
||||
// fourXXRate: 0.6325778,
|
||||
// },
|
||||
// {
|
||||
// serviceName: "driver",
|
||||
// p99: 243808000,
|
||||
// avgDuration: 204640670,
|
||||
// numCalls: 1898,
|
||||
// callRate: 0.000019097411,
|
||||
// numErrors: 0,
|
||||
// errorRate: 0,
|
||||
// num4XX: 0,
|
||||
// fourXXRate: 0.63224447,
|
||||
// },
|
||||
// {
|
||||
// serviceName: "route",
|
||||
// p99: 79419000,
|
||||
// avgDuration: 50748804,
|
||||
// numCalls: 18979,
|
||||
// callRate: 0.00019096404,
|
||||
// numErrors: 0,
|
||||
// errorRate: 3,
|
||||
// num4XX: 0,
|
||||
// fourXXRate: 0,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// const response = await api.get<servicesItem[]>(apiV1 + request_string);
|
||||
const response = {
|
||||
data: [
|
||||
{
|
||||
serviceName: "redisredisredisredisredisredisredisredisre",
|
||||
p99: 1179518000,
|
||||
avgDuration: 742158850,
|
||||
numCalls: 1898,
|
||||
callRate: 0.019097411,
|
||||
numErrors: 0,
|
||||
errorRate: 3,
|
||||
num4XX: 0,
|
||||
fourXXRate: 0,
|
||||
},
|
||||
{
|
||||
serviceName: "mysql",
|
||||
p99: 1179518000,
|
||||
avgDuration: 742158850,
|
||||
numCalls: 1898,
|
||||
callRate: 0.019097411,
|
||||
numErrors: 0,
|
||||
errorRate: 0,
|
||||
num4XX: 0,
|
||||
fourXXRate: 0,
|
||||
},
|
||||
{
|
||||
serviceName: "frontend",
|
||||
p99: 1179518000,
|
||||
avgDuration: 742158850,
|
||||
numCalls: 1898,
|
||||
callRate: 0.000019097411,
|
||||
numErrors: 0,
|
||||
errorRate: 0,
|
||||
num4XX: 0,
|
||||
fourXXRate: 0,
|
||||
},
|
||||
{
|
||||
serviceName: "customer",
|
||||
p99: 728385000,
|
||||
avgDuration: 342475680,
|
||||
numCalls: 1897,
|
||||
callRate: 0.000019087349,
|
||||
numErrors: 0,
|
||||
errorRate: 0,
|
||||
num4XX: 0,
|
||||
fourXXRate: 0.6325778,
|
||||
},
|
||||
{
|
||||
serviceName: "driver",
|
||||
p99: 243808000,
|
||||
avgDuration: 204640670,
|
||||
numCalls: 1898,
|
||||
callRate: 0.000019097411,
|
||||
numErrors: 0,
|
||||
errorRate: 0,
|
||||
num4XX: 0,
|
||||
fourXXRate: 0.63224447,
|
||||
},
|
||||
{
|
||||
serviceName: "route",
|
||||
p99: 79419000,
|
||||
avgDuration: 50748804,
|
||||
numCalls: 18979,
|
||||
callRate: 0.00019096404,
|
||||
numErrors: 0,
|
||||
errorRate: 3,
|
||||
num4XX: 0,
|
||||
fourXXRate: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
dispatch<servicesAction>({
|
||||
type: ActionTypes.getServices,
|
||||
payload: response.data,
|
||||
|
Loading…
x
Reference in New Issue
Block a user