mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-09-23 01:33:12 +08:00
initial set up with react-force-graph
This commit is contained in:
parent
44666a4944
commit
c7ed2daf4a
21596
frontend/package-lock.json
generated
Normal file
21596
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -88,6 +88,7 @@
|
||||
"react-css-theme-switcher": "^0.1.6",
|
||||
"react-dev-utils": "^11.0.0",
|
||||
"react-dom": "17.0.0",
|
||||
"react-force-graph": "^1.41.0",
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-modal": "^3.12.1",
|
||||
"react-redux": "^7.2.2",
|
||||
|
@ -1,71 +0,0 @@
|
||||
import React from "react";
|
||||
// import {useState} from "react";
|
||||
import Graph from "react-graph-vis";
|
||||
// import { graphEvents } from "react-graph-vis";
|
||||
|
||||
//PNOTE - types of react-graph-vis defined in typings folder.
|
||||
//How is it imported directly?
|
||||
// type definition for service graph - https://github.com/crubier/react-graph-vis/issues/80
|
||||
|
||||
// Set shapes - https://visjs.github.io/vis-network/docs/network/nodes.html#
|
||||
// https://github.com/crubier/react-graph-vis/issues/93
|
||||
const graph = {
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
label: "Catalogue",
|
||||
shape: "box",
|
||||
color: "green",
|
||||
border: "black",
|
||||
size: 100,
|
||||
},
|
||||
{ id: 2, label: "Users", shape: "box", color: "#FFFF00" },
|
||||
{ id: 3, label: "Payment App", shape: "box", color: "#FB7E81" },
|
||||
{ id: 4, label: "My Sql", shape: "box", size: 10, color: "#7BE141" },
|
||||
{ id: 5, label: "Redis-db", shape: "box", color: "#6E6EFD" },
|
||||
],
|
||||
edges: [
|
||||
{ from: 1, to: 2, color: { color: "red" }, size: { size: 20 } },
|
||||
{ from: 2, to: 3, color: { color: "red" } },
|
||||
{ from: 1, to: 3, color: { color: "red" } },
|
||||
{ from: 3, to: 4, color: { color: "red" } },
|
||||
{ from: 3, to: 5, color: { color: "red" } },
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
hierarchical: true,
|
||||
},
|
||||
edges: {
|
||||
color: "#000000",
|
||||
},
|
||||
height: "500px",
|
||||
};
|
||||
|
||||
// const events = {
|
||||
// select: function(event:any) { //PNOTE - TO DO - Get rid of any type
|
||||
// var { nodes, edges } = event;
|
||||
// }
|
||||
// };
|
||||
|
||||
const ServiceGraph = () => {
|
||||
// const [network, setNetwork] = useState(null);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div> Updated Service Graph module coming soon..</div>
|
||||
|
||||
<Graph
|
||||
graph={graph}
|
||||
options={options}
|
||||
// events={events}
|
||||
// getNetwork={network => {
|
||||
// // if you want access to vis.js network api you can set the state in a parent component using this property
|
||||
// }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceGraph;
|
@ -1,14 +1,114 @@
|
||||
import React from "react";
|
||||
import ServiceGraph from "./ServiceGraph";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import {
|
||||
GlobalTime,
|
||||
serviceMapStore,
|
||||
getServiceMapItems,
|
||||
getDetailedServiceMapItems,
|
||||
} from "Src/store/actions";
|
||||
import { StoreState } from "../../store/reducers";
|
||||
import { getGraphData } from "./utils";
|
||||
import { ForceGraph2D } from "react-force-graph";
|
||||
|
||||
const ServiceMap = () => {
|
||||
interface ServiceMapProps extends RouteComponentProps<any> {
|
||||
serviceMap: serviceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
getServiceMapItems: Function;
|
||||
getDetailedServiceMapItems: Function;
|
||||
}
|
||||
interface graphNode {
|
||||
id: string;
|
||||
group: number;
|
||||
}
|
||||
interface graphLink {
|
||||
source: string;
|
||||
target: string;
|
||||
value: number;
|
||||
}
|
||||
export interface graphDataType {
|
||||
nodes: graphNode[];
|
||||
links: graphLink[];
|
||||
}
|
||||
|
||||
const ServiceMap = (props: ServiceMapProps) => {
|
||||
const fgRef = useRef();
|
||||
const {
|
||||
getDetailedServiceMapItems,
|
||||
getServiceMapItems,
|
||||
globalTime,
|
||||
serviceMap,
|
||||
} = props;
|
||||
useEffect(() => {
|
||||
getServiceMapItems(globalTime);
|
||||
getDetailedServiceMapItems(globalTime);
|
||||
}, []);
|
||||
|
||||
if (!serviceMap.items.length) {
|
||||
return "loading";
|
||||
}
|
||||
const { nodes, links } = getGraphData(serviceMap.items);
|
||||
const graphData = { nodes, links };
|
||||
return (
|
||||
<div>
|
||||
{" "}
|
||||
Service Map module coming soon...
|
||||
{/* <ServiceGraph /> */}
|
||||
<ForceGraph2D
|
||||
ref={fgRef}
|
||||
cooldownTicks={100}
|
||||
onEngineStop={() => fgRef.current.zoomToFit(100, 100)}
|
||||
graphData={graphData}
|
||||
nodeLabel="id"
|
||||
nodeAutoColorBy={(d) => d.id}
|
||||
linkAutoColorBy={(d) => d.target}
|
||||
linkDirectionalParticles="value"
|
||||
linkDirectionalParticleSpeed={(d) => d.value}
|
||||
nodeCanvasObject={(node, ctx, globalScale) => {
|
||||
const label = node.id;
|
||||
const fontSize = 16 / globalScale;
|
||||
ctx.font = `${fontSize}px Sans-Serif`;
|
||||
const textWidth = ctx.measureText(label).width;
|
||||
const bckgDimensions = [textWidth, fontSize].map((n) => n + fontSize); // some padding
|
||||
ctx.fillStyle = node.color;
|
||||
ctx.fillRect(
|
||||
node.x - bckgDimensions[0] / 2,
|
||||
node.y - bckgDimensions[1] / 2,
|
||||
...bckgDimensions,
|
||||
);
|
||||
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillText(label, node.x, node.y);
|
||||
|
||||
node.__bckgDimensions = bckgDimensions; // to re-use in nodePointerAreaPaint
|
||||
}}
|
||||
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,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceMap;
|
||||
const mapStateToProps = (
|
||||
state: StoreState,
|
||||
): {
|
||||
serviceMap: serviceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
} => {
|
||||
return {
|
||||
serviceMap: state.serviceMap,
|
||||
globalTime: state.globalTime,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getServiceMapItems: getServiceMapItems,
|
||||
getDetailedServiceMapItems: getDetailedServiceMapItems,
|
||||
})(ServiceMap);
|
||||
|
1
frontend/src/modules/Servicemap/index.ts
Normal file
1
frontend/src/modules/Servicemap/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./ServiceMap";
|
28
frontend/src/modules/Servicemap/utils.ts
Normal file
28
frontend/src/modules/Servicemap/utils.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { uniqBy, uniq, maxBy, cloneDeep } from "lodash";
|
||||
import { servicesMapItem } from "Src/store/actions";
|
||||
import { graphDataType } from "./ServiceMap";
|
||||
|
||||
export const getGraphData = (item: servicesMapItem[]): graphDataType => {
|
||||
const highestNum = maxBy(item, (e) => e.callCount).callCount;
|
||||
const divNum = Number(String(1).padEnd(highestNum.toString().length, "0"));
|
||||
|
||||
const links = cloneDeep(item).map((node) => {
|
||||
const { parent, child, callCount } = node;
|
||||
return {
|
||||
source: parent,
|
||||
target: child,
|
||||
value: (100 - callCount / divNum) * 0.01,
|
||||
};
|
||||
});
|
||||
const uniqParent = uniqBy(cloneDeep(item), "parent").map((e) => e.parent);
|
||||
const uniqChild = uniqBy(cloneDeep(item), "child").map((e) => e.child);
|
||||
const uniqNodes = uniq([...uniqParent, ...uniqChild]);
|
||||
const nodes = uniqNodes.map((node, i) => ({
|
||||
id: node,
|
||||
group: i + 1,
|
||||
}));
|
||||
return {
|
||||
nodes,
|
||||
links,
|
||||
};
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
export * from "./types";
|
||||
export * from "./traceFilters";
|
||||
export * from "./serviceMap";
|
||||
export * from "./traces";
|
||||
export * from "./metrics";
|
||||
export * from "./usage";
|
||||
|
96
frontend/src/store/actions/serviceMap.ts
Normal file
96
frontend/src/store/actions/serviceMap.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { Dispatch } from "redux";
|
||||
import api, { apiV1 } from "../../api";
|
||||
import { GlobalTime } from "./global";
|
||||
import { ActionTypes } from "./types";
|
||||
|
||||
export interface serviceMapStore {
|
||||
items: servicesMapItem[];
|
||||
services: servicesItem[];
|
||||
}
|
||||
|
||||
export interface servicesItem {
|
||||
serviceName: string;
|
||||
p99: number;
|
||||
avgDuration: number;
|
||||
numCalls: number;
|
||||
callRate: number;
|
||||
numErrors: number;
|
||||
errorRate: number;
|
||||
num4XX: number;
|
||||
fourXXRate: number;
|
||||
}
|
||||
|
||||
export interface servicesMapItem {
|
||||
parent: string;
|
||||
child: string;
|
||||
callCount: number;
|
||||
}
|
||||
|
||||
export interface serviceMapItemAction {
|
||||
type: ActionTypes.getServiceMapItems;
|
||||
payload: servicesMapItem[];
|
||||
}
|
||||
|
||||
export interface servicesAction {
|
||||
type: ActionTypes.getServices;
|
||||
payload: servicesItem[];
|
||||
}
|
||||
|
||||
export const getServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string =
|
||||
"/serviceMapDependencies?start=" +
|
||||
globalTime.minTime +
|
||||
"&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,
|
||||
},
|
||||
],
|
||||
};
|
||||
dispatch<serviceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: response.data,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string =
|
||||
"/services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime;
|
||||
|
||||
const response = await api.get<servicesItem[]>(apiV1 + request_string);
|
||||
|
||||
dispatch<servicesAction>({
|
||||
type: ActionTypes.getServices,
|
||||
payload: response.data,
|
||||
});
|
||||
};
|
||||
};
|
@ -10,6 +10,7 @@ import {
|
||||
getFilteredTraceMetricsAction,
|
||||
getDbOverViewMetricsAction,
|
||||
} from "./metrics";
|
||||
import { serviceMapItemAction, servicesAction } from "./serviceMap";
|
||||
import { getUsageDataAction } from "./usage";
|
||||
import { updateTimeIntervalAction } from "./global";
|
||||
|
||||
@ -28,6 +29,8 @@ export enum ActionTypes {
|
||||
getUsageData = "GET_USAGE_DATE",
|
||||
updateTimeInterval = "UPDATE_TIME_INTERVAL",
|
||||
getFilteredTraceMetrics = "GET_FILTERED_TRACE_METRICS",
|
||||
getServiceMapItems = "GET_SERVICE_MAP_ITEMS",
|
||||
getServices = "GET_SERVICES",
|
||||
}
|
||||
|
||||
export type Action =
|
||||
@ -44,4 +47,6 @@ export type Action =
|
||||
| getExternalMetricsAction
|
||||
| externalErrCodeMetricsActions
|
||||
| getDbOverViewMetricsAction
|
||||
| servicesAction
|
||||
| serviceMapItemAction
|
||||
| externalMetricsAvgDurationAction;
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
usageDataItem,
|
||||
GlobalTime,
|
||||
externalErrCodeMetricsItem,
|
||||
serviceMapStore,
|
||||
customMetricsItem,
|
||||
TraceFilters,
|
||||
} from "../actions";
|
||||
@ -27,7 +28,7 @@ import {
|
||||
import { traceFiltersReducer, inputsReducer } from "./traceFilters";
|
||||
import { traceItemReducer, tracesReducer } from "./traces";
|
||||
import { usageDataReducer } from "./usage";
|
||||
|
||||
import { ServiceMapReducer } from "./serviceMap";
|
||||
export interface StoreState {
|
||||
traceFilters: TraceFilters;
|
||||
inputTag: string;
|
||||
@ -43,6 +44,7 @@ export interface StoreState {
|
||||
usageDate: usageDataItem[];
|
||||
globalTime: GlobalTime;
|
||||
filteredTraceMetrics: customMetricsItem[];
|
||||
serviceMap: serviceMapStore;
|
||||
}
|
||||
|
||||
const reducers = combineReducers<StoreState>({
|
||||
@ -60,6 +62,7 @@ const reducers = combineReducers<StoreState>({
|
||||
usageDate: usageDataReducer,
|
||||
globalTime: updateGlobalTimeReducer,
|
||||
filteredTraceMetrics: filteredTraceMetricsReducer,
|
||||
serviceMap: ServiceMapReducer,
|
||||
});
|
||||
|
||||
export default reducers;
|
||||
|
24
frontend/src/store/reducers/serviceMap.ts
Normal file
24
frontend/src/store/reducers/serviceMap.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { ActionTypes, Action, serviceMapStore } from "../actions";
|
||||
|
||||
export const ServiceMapReducer = (
|
||||
state: serviceMapStore = {
|
||||
items: [],
|
||||
services: [],
|
||||
},
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getServiceMapItems:
|
||||
return {
|
||||
...state,
|
||||
items: action.payload,
|
||||
};
|
||||
case ActionTypes.getServices:
|
||||
return {
|
||||
...state,
|
||||
services: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user