mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-09-23 03:03:14 +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-css-theme-switcher": "^0.1.6",
|
||||||
"react-dev-utils": "^11.0.0",
|
"react-dev-utils": "^11.0.0",
|
||||||
"react-dom": "17.0.0",
|
"react-dom": "17.0.0",
|
||||||
|
"react-force-graph": "^1.41.0",
|
||||||
"react-graph-vis": "^1.0.5",
|
"react-graph-vis": "^1.0.5",
|
||||||
"react-modal": "^3.12.1",
|
"react-modal": "^3.12.1",
|
||||||
"react-redux": "^7.2.2",
|
"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 React, { useEffect, useRef } from "react";
|
||||||
import ServiceGraph from "./ServiceGraph";
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
<ForceGraph2D
|
||||||
Service Map module coming soon...
|
ref={fgRef}
|
||||||
{/* <ServiceGraph /> */}
|
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>
|
</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 "./types";
|
||||||
export * from "./traceFilters";
|
export * from "./traceFilters";
|
||||||
|
export * from "./serviceMap";
|
||||||
export * from "./traces";
|
export * from "./traces";
|
||||||
export * from "./metrics";
|
export * from "./metrics";
|
||||||
export * from "./usage";
|
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,
|
getFilteredTraceMetricsAction,
|
||||||
getDbOverViewMetricsAction,
|
getDbOverViewMetricsAction,
|
||||||
} from "./metrics";
|
} from "./metrics";
|
||||||
|
import { serviceMapItemAction, servicesAction } from "./serviceMap";
|
||||||
import { getUsageDataAction } from "./usage";
|
import { getUsageDataAction } from "./usage";
|
||||||
import { updateTimeIntervalAction } from "./global";
|
import { updateTimeIntervalAction } from "./global";
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ export enum ActionTypes {
|
|||||||
getUsageData = "GET_USAGE_DATE",
|
getUsageData = "GET_USAGE_DATE",
|
||||||
updateTimeInterval = "UPDATE_TIME_INTERVAL",
|
updateTimeInterval = "UPDATE_TIME_INTERVAL",
|
||||||
getFilteredTraceMetrics = "GET_FILTERED_TRACE_METRICS",
|
getFilteredTraceMetrics = "GET_FILTERED_TRACE_METRICS",
|
||||||
|
getServiceMapItems = "GET_SERVICE_MAP_ITEMS",
|
||||||
|
getServices = "GET_SERVICES",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
@ -44,4 +47,6 @@ export type Action =
|
|||||||
| getExternalMetricsAction
|
| getExternalMetricsAction
|
||||||
| externalErrCodeMetricsActions
|
| externalErrCodeMetricsActions
|
||||||
| getDbOverViewMetricsAction
|
| getDbOverViewMetricsAction
|
||||||
|
| servicesAction
|
||||||
|
| serviceMapItemAction
|
||||||
| externalMetricsAvgDurationAction;
|
| externalMetricsAvgDurationAction;
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
usageDataItem,
|
usageDataItem,
|
||||||
GlobalTime,
|
GlobalTime,
|
||||||
externalErrCodeMetricsItem,
|
externalErrCodeMetricsItem,
|
||||||
|
serviceMapStore,
|
||||||
customMetricsItem,
|
customMetricsItem,
|
||||||
TraceFilters,
|
TraceFilters,
|
||||||
} from "../actions";
|
} from "../actions";
|
||||||
@ -27,7 +28,7 @@ import {
|
|||||||
import { traceFiltersReducer, inputsReducer } from "./traceFilters";
|
import { traceFiltersReducer, inputsReducer } from "./traceFilters";
|
||||||
import { traceItemReducer, tracesReducer } from "./traces";
|
import { traceItemReducer, tracesReducer } from "./traces";
|
||||||
import { usageDataReducer } from "./usage";
|
import { usageDataReducer } from "./usage";
|
||||||
|
import { ServiceMapReducer } from "./serviceMap";
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
traceFilters: TraceFilters;
|
traceFilters: TraceFilters;
|
||||||
inputTag: string;
|
inputTag: string;
|
||||||
@ -43,6 +44,7 @@ export interface StoreState {
|
|||||||
usageDate: usageDataItem[];
|
usageDate: usageDataItem[];
|
||||||
globalTime: GlobalTime;
|
globalTime: GlobalTime;
|
||||||
filteredTraceMetrics: customMetricsItem[];
|
filteredTraceMetrics: customMetricsItem[];
|
||||||
|
serviceMap: serviceMapStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducers = combineReducers<StoreState>({
|
const reducers = combineReducers<StoreState>({
|
||||||
@ -60,6 +62,7 @@ const reducers = combineReducers<StoreState>({
|
|||||||
usageDate: usageDataReducer,
|
usageDate: usageDataReducer,
|
||||||
globalTime: updateGlobalTimeReducer,
|
globalTime: updateGlobalTimeReducer,
|
||||||
filteredTraceMetrics: filteredTraceMetricsReducer,
|
filteredTraceMetrics: filteredTraceMetricsReducer,
|
||||||
|
serviceMap: ServiceMapReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default reducers;
|
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