UX updates

This commit is contained in:
dhrubesh 2021-05-09 22:51:08 +05:30
parent 325ca434d4
commit 9ac2dece11
6 changed files with 200 additions and 165 deletions

View File

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

View File

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

View File

@ -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,6 +35,7 @@ const SelectService = (props: SelectServiceProps) => {
zoomToService(value);
};
return (
<Container>
<Select
style={{ width: 270, marginBottom: "56px" }}
placeholder="Select a service"
@ -28,6 +48,15 @@ const SelectService = (props: SelectServiceProps) => {
</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>
);
};

View File

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

View File

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

View File

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