mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 01:19:02 +08:00
Sanity prettify
This commit is contained in:
parent
ff47f0978f
commit
5ff2d9e9e7
7
frontend/.prettierrc.json
Normal file
7
frontend/.prettierrc.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 1,
|
||||||
|
"singleQuote": false,
|
||||||
|
"jsxSingleQuote": false
|
||||||
|
}
|
@ -1,28 +1,28 @@
|
|||||||
const gulp = require('gulp')
|
const gulp = require("gulp");
|
||||||
const gulpless = require('gulp-less')
|
const gulpless = require("gulp-less");
|
||||||
const postcss = require('gulp-postcss')
|
const postcss = require("gulp-postcss");
|
||||||
const debug = require('gulp-debug')
|
const debug = require("gulp-debug");
|
||||||
var csso = require('gulp-csso')
|
var csso = require("gulp-csso");
|
||||||
const autoprefixer = require('autoprefixer')
|
const autoprefixer = require("autoprefixer");
|
||||||
const NpmImportPlugin = require('less-plugin-npm-import')
|
const NpmImportPlugin = require("less-plugin-npm-import");
|
||||||
|
|
||||||
gulp.task('less', function () {
|
gulp.task("less", function () {
|
||||||
const plugins = [autoprefixer()]
|
const plugins = [autoprefixer()];
|
||||||
|
|
||||||
return gulp
|
return gulp
|
||||||
.src('src/themes/*-theme.less')
|
.src("src/themes/*-theme.less")
|
||||||
.pipe(debug({title: 'Less files:'}))
|
.pipe(debug({ title: "Less files:" }))
|
||||||
.pipe(
|
.pipe(
|
||||||
gulpless({
|
gulpless({
|
||||||
javascriptEnabled: true,
|
javascriptEnabled: true,
|
||||||
plugins: [new NpmImportPlugin({prefix: '~'})],
|
plugins: [new NpmImportPlugin({ prefix: "~" })],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.pipe(postcss(plugins))
|
.pipe(postcss(plugins))
|
||||||
.pipe(
|
.pipe(
|
||||||
csso({
|
csso({
|
||||||
debug: true,
|
debug: true,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.pipe(gulp.dest('./public'))
|
.pipe(gulp.dest("./public"));
|
||||||
})
|
});
|
||||||
|
@ -1,87 +1,91 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.0.0",
|
"@material-ui/core": "^4.0.0",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"@types/chart.js": "^2.9.28",
|
"@types/chart.js": "^2.9.28",
|
||||||
"@types/d3": "^6.2.0",
|
"@types/d3": "^6.2.0",
|
||||||
"@types/jest": "^26.0.15",
|
"@types/jest": "^26.0.15",
|
||||||
"@types/node": "^14.14.7",
|
"@types/node": "^14.14.7",
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"@types/react-dom": "^16.9.9",
|
"@types/react-dom": "^16.9.9",
|
||||||
"@types/react-redux": "^7.1.11",
|
"@types/react-redux": "^7.1.11",
|
||||||
"@types/react-router-dom": "^5.1.6",
|
"@types/react-router-dom": "^5.1.6",
|
||||||
"@types/redux": "^3.6.0",
|
"@types/redux": "^3.6.0",
|
||||||
"@types/styled-components": "^5.1.4",
|
"@types/styled-components": "^5.1.4",
|
||||||
"@types/vis": "^4.21.21",
|
"@types/vis": "^4.21.21",
|
||||||
"antd": "^4.8.0",
|
"antd": "^4.8.0",
|
||||||
"axios": "^0.21.0",
|
"axios": "^0.21.0",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"d3": "^6.2.0",
|
"d3": "^6.2.0",
|
||||||
"d3-array": "^2.8.0",
|
"d3-array": "^2.8.0",
|
||||||
"d3-ease": "^2.0.0",
|
"d3-ease": "^2.0.0",
|
||||||
"d3-flame-graph": "^3.1.1",
|
"d3-flame-graph": "^3.1.1",
|
||||||
"d3-tip": "^0.9.1",
|
"d3-tip": "^0.9.1",
|
||||||
"material-ui-chip-input": "^2.0.0-beta.2",
|
"material-ui-chip-input": "^2.0.0-beta.2",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "17.0.0",
|
"react": "17.0.0",
|
||||||
"react-chartjs-2": "^2.11.1",
|
"react-chartjs-2": "^2.11.1",
|
||||||
"react-chips": "^0.8.0",
|
"react-chips": "^0.8.0",
|
||||||
"react-css-theme-switcher": "^0.1.6",
|
"react-css-theme-switcher": "^0.1.6",
|
||||||
"react-dom": "17.0.0",
|
"react-dom": "17.0.0",
|
||||||
"react-graph-vis": "^1.0.5",
|
"react-graph-vis": "^1.0.5",
|
||||||
"react-redux": "^7.2.2",
|
"react-redux": "^7.2.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.0",
|
"react-scripts": "4.0.0",
|
||||||
"react-vis": "^1.11.7",
|
"react-vis": "^1.11.7",
|
||||||
"recharts": "^1.8.5",
|
"recharts": "^1.8.5",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"styled-components": "^5.2.1",
|
"styled-components": "^5.2.1",
|
||||||
"typescript": "^4.0.5",
|
"typescript": "^4.0.5",
|
||||||
"web-vitals": "^0.2.4"
|
"web-vitals": "^0.2.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"storybook": "start-storybook -p 6006 -s public --no-dll",
|
"storybook": "start-storybook -p 6006 -s public --no-dll",
|
||||||
"build-storybook": "build-storybook -s public --no-dll"
|
"build-storybook": "build-storybook -s public --no-dll",
|
||||||
},
|
"prettify": "prettier --write ."
|
||||||
"eslintConfig": {
|
},
|
||||||
"extends": [
|
"eslintConfig": {
|
||||||
"react-app",
|
"extends": [
|
||||||
"react-app/jest"
|
"react-app",
|
||||||
]
|
"react-app/jest"
|
||||||
},
|
]
|
||||||
"browserslist": {
|
},
|
||||||
"production": [
|
"browserslist": {
|
||||||
">0.2%",
|
"production": [
|
||||||
"not dead",
|
">0.2%",
|
||||||
"not op_mini all"
|
"not dead",
|
||||||
],
|
"not op_mini all"
|
||||||
"development": [
|
],
|
||||||
"last 1 chrome version",
|
"development": [
|
||||||
"last 1 firefox version",
|
"last 1 chrome version",
|
||||||
"last 1 safari version"
|
"last 1 firefox version",
|
||||||
]
|
"last 1 safari version"
|
||||||
},
|
]
|
||||||
"devDependencies": {
|
},
|
||||||
"@babel/core": "^7.12.3",
|
"devDependencies": {
|
||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/core": "^7.12.3",
|
||||||
"autoprefixer": "^9.0.0",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"babel-plugin-styled-components": "^1.12.0",
|
"autoprefixer": "^9.0.0",
|
||||||
"gulp": "^4.0.2",
|
"babel-plugin-styled-components": "^1.12.0",
|
||||||
"gulp-csso": "^4.0.1",
|
"gulp": "^4.0.2",
|
||||||
"gulp-debug": "^4.0.0",
|
"gulp-csso": "^4.0.1",
|
||||||
"gulp-less": "^4.0.1",
|
"gulp-debug": "^4.0.0",
|
||||||
"gulp-postcss": "^9.0.0",
|
"gulp-less": "^4.0.1",
|
||||||
"less-plugin-npm-import": "^2.1.0",
|
"gulp-postcss": "^9.0.0",
|
||||||
"react-is": "^17.0.1"
|
"husky": "4.3.8",
|
||||||
}
|
"less-plugin-npm-import": "^2.1.0",
|
||||||
|
"lint-staged": "10.5.3",
|
||||||
|
"prettier": "2.2.1",
|
||||||
|
"react-is": "^17.0.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,22 +1,24 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Web site created using create-react-app" />
|
||||||
name="description"
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
content="Web site created using create-react-app"
|
<link
|
||||||
/>
|
rel="stylesheet"
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
|
||||||
<!--
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
@ -25,12 +27,12 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>React App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
This HTML file is a template.
|
||||||
If you open it directly in the browser, you will see an empty page.
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
@ -40,5 +42,5 @@
|
|||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"short_name": "React App",
|
"short_name": "React App",
|
||||||
"name": "Create React App Sample",
|
"name": "Create React App Sample",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
"type": "image/x-icon"
|
"type": "image/x-icon"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "logo192.png",
|
"src": "logo192.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "192x192"
|
"sizes": "192x192"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "logo512.png",
|
"src": "logo512.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "512x512"
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"background_color": "#ffffff"
|
"background_color": "#ffffff"
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,71 @@
|
|||||||
import { ActionTypes } from './types';
|
import { ActionTypes } from "./types";
|
||||||
import { Moment } from 'moment'
|
import { Moment } from "moment";
|
||||||
|
|
||||||
|
|
||||||
export type DateTimeRangeType = [Moment|null,Moment|null]|null;
|
|
||||||
|
|
||||||
|
export type DateTimeRangeType = [Moment | null, Moment | null] | null;
|
||||||
|
|
||||||
export interface GlobalTime {
|
export interface GlobalTime {
|
||||||
maxTime: number;
|
maxTime: number;
|
||||||
minTime: number;
|
minTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface updateTimeIntervalAction {
|
export interface updateTimeIntervalAction {
|
||||||
type: ActionTypes.updateTimeInterval;
|
type: ActionTypes.updateTimeInterval;
|
||||||
payload: GlobalTime;
|
payload: GlobalTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateTimeInterval = (interval:string, datetimeRange?:[number,number]) => {
|
export const updateTimeInterval = (
|
||||||
|
interval: string,
|
||||||
|
datetimeRange?: [number, number],
|
||||||
|
) => {
|
||||||
|
let maxTime: number = 0;
|
||||||
|
let minTime: number = 0;
|
||||||
|
// if interval string is custom, then datetimRange should be present and max & min time should be
|
||||||
|
// set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element
|
||||||
|
|
||||||
let maxTime: number = 0;
|
switch (interval) {
|
||||||
let minTime: number = 0;
|
case "15min":
|
||||||
// if interval string is custom, then datetimRange should be present and max & min time should be
|
maxTime = Date.now() * 1000000; // in nano sec
|
||||||
// set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element
|
minTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
||||||
|
break;
|
||||||
|
|
||||||
switch (interval) {
|
case "30min":
|
||||||
case '15min':
|
maxTime = Date.now() * 1000000; // in nano sec
|
||||||
|
minTime = (Date.now() - 30 * 60 * 1000) * 1000000;
|
||||||
maxTime=Date.now()*1000000; // in nano sec
|
break;
|
||||||
minTime=(Date.now()-15*60*1000)*1000000;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '30min':
|
|
||||||
maxTime=Date.now()*1000000; // in nano sec
|
|
||||||
minTime=(Date.now()-30*60*1000)*1000000;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '1hr':
|
case "1hr":
|
||||||
maxTime=Date.now()*1000000; // in nano sec
|
maxTime = Date.now() * 1000000; // in nano sec
|
||||||
minTime=(Date.now()-1*60*60*1000)*1000000;
|
minTime = (Date.now() - 1 * 60 * 60 * 1000) * 1000000;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '6hr':
|
case "6hr":
|
||||||
maxTime=Date.now()*1000000; // in nano sec
|
maxTime = Date.now() * 1000000; // in nano sec
|
||||||
minTime=(Date.now()-6*60*60*1000)*1000000;
|
minTime = (Date.now() - 6 * 60 * 60 * 1000) * 1000000;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '1day':
|
|
||||||
maxTime=Date.now()*1000000; // in nano sec
|
|
||||||
minTime=(Date.now()-24*60*60*1000)*1000000;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '1week':
|
case "1day":
|
||||||
maxTime=Date.now()*1000000; // in nano sec
|
maxTime = Date.now() * 1000000; // in nano sec
|
||||||
minTime=(Date.now()-7*24*60*60*1000)*1000000;
|
minTime = (Date.now() - 24 * 60 * 60 * 1000) * 1000000;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'custom':
|
case "1week":
|
||||||
if (datetimeRange !== undefined)
|
maxTime = Date.now() * 1000000; // in nano sec
|
||||||
{
|
minTime = (Date.now() - 7 * 24 * 60 * 60 * 1000) * 1000000;
|
||||||
maxTime=datetimeRange[1]*1000000;// in nano sec
|
break;
|
||||||
minTime=datetimeRange[0]*1000000;// in nano sec
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
case "custom":
|
||||||
console.log('not found matching case')
|
if (datetimeRange !== undefined) {
|
||||||
|
maxTime = datetimeRange[1] * 1000000; // in nano sec
|
||||||
}
|
minTime = datetimeRange[0] * 1000000; // in nano sec
|
||||||
|
}
|
||||||
|
break;
|
||||||
return {
|
|
||||||
type: ActionTypes.updateTimeInterval,
|
default:
|
||||||
payload: {maxTime:maxTime, minTime:minTime},
|
console.log("not found matching case");
|
||||||
};
|
}
|
||||||
};
|
|
||||||
|
return {
|
||||||
|
type: ActionTypes.updateTimeInterval,
|
||||||
|
payload: { maxTime: maxTime, minTime: minTime },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export * from './types';
|
export * from "./types";
|
||||||
export * from './traceFilters';
|
export * from "./traceFilters";
|
||||||
export * from './traces';
|
export * from "./traces";
|
||||||
export * from './metrics';
|
export * from "./metrics";
|
||||||
export * from './usage';
|
export * from "./usage";
|
||||||
export * from './global';
|
export * from "./global";
|
||||||
|
@ -1,112 +1,139 @@
|
|||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from "redux";
|
||||||
import metricsAPI from '../api/metricsAPI';
|
import metricsAPI from "../api/metricsAPI";
|
||||||
import { GlobalTime } from './global';
|
import { GlobalTime } from "./global";
|
||||||
import { ActionTypes } from './types';
|
import { ActionTypes } from "./types";
|
||||||
|
|
||||||
export interface servicesListItem{
|
export interface servicesListItem {
|
||||||
"serviceName": string;
|
serviceName: string;
|
||||||
"p99": number;
|
p99: number;
|
||||||
"avgDuration": number;
|
avgDuration: number;
|
||||||
"numCalls": number;
|
numCalls: number;
|
||||||
"callRate": number;
|
callRate: number;
|
||||||
"numErrors": number;
|
numErrors: number;
|
||||||
"errorRate": number;
|
errorRate: number;
|
||||||
};
|
|
||||||
|
|
||||||
export interface metricItem{
|
|
||||||
"timestamp":number;
|
|
||||||
"p50":number;
|
|
||||||
"p90":number;
|
|
||||||
"p99":number;
|
|
||||||
"numCalls":number;
|
|
||||||
"callRate":number;
|
|
||||||
"numErrors":number;
|
|
||||||
"errorRate":number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface topEndpointListItem{
|
export interface metricItem {
|
||||||
"p50": number;
|
timestamp: number;
|
||||||
"p90": number;
|
p50: number;
|
||||||
"p99": number;
|
p90: number;
|
||||||
"numCalls": number;
|
p99: number;
|
||||||
"name": string;
|
numCalls: number;
|
||||||
};
|
callRate: number;
|
||||||
|
numErrors: number;
|
||||||
|
errorRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface customMetricsItem{
|
export interface topEndpointListItem {
|
||||||
"timestamp": number;
|
p50: number;
|
||||||
"value": number;
|
p90: number;
|
||||||
};
|
p99: number;
|
||||||
|
numCalls: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface customMetricsItem {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface getServicesListAction {
|
export interface getServicesListAction {
|
||||||
type: ActionTypes.getServicesList;
|
type: ActionTypes.getServicesList;
|
||||||
payload: servicesListItem[];
|
payload: servicesListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface getServiceMetricsAction{
|
export interface getServiceMetricsAction {
|
||||||
type: ActionTypes.getServiceMetrics;
|
type: ActionTypes.getServiceMetrics;
|
||||||
payload: metricItem[];
|
payload: metricItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface getTopEndpointsAction {
|
export interface getTopEndpointsAction {
|
||||||
type: ActionTypes.getTopEndpoints;
|
type: ActionTypes.getTopEndpoints;
|
||||||
payload: topEndpointListItem[];
|
payload: topEndpointListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface getFilteredTraceMetricsAction{
|
export interface getFilteredTraceMetricsAction {
|
||||||
type: ActionTypes.getFilteredTraceMetrics;
|
type: ActionTypes.getFilteredTraceMetrics;
|
||||||
payload: customMetricsItem[];
|
payload: customMetricsItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getServicesList = (globalTime: GlobalTime) => {
|
export const getServicesList = (globalTime: GlobalTime) => {
|
||||||
return async (dispatch: Dispatch) => {
|
return async (dispatch: Dispatch) => {
|
||||||
let request_string = 'services?start='+globalTime.minTime+'&end='+globalTime.maxTime;
|
let request_string =
|
||||||
const response = await metricsAPI.get<servicesListItem[]>(request_string);
|
"services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime;
|
||||||
|
const response = await metricsAPI.get<servicesListItem[]>(request_string);
|
||||||
dispatch<getServicesListAction>({
|
|
||||||
type: ActionTypes.getServicesList,
|
dispatch<getServicesListAction>({
|
||||||
payload: response.data
|
type: ActionTypes.getServicesList,
|
||||||
//PNOTE - response.data in the axios response has the actual API response
|
payload: response.data,
|
||||||
});
|
//PNOTE - response.data in the axios response has the actual API response
|
||||||
};
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getServicesMetrics = (serviceName:string, globalTime: GlobalTime) => {
|
export const getServicesMetrics = (
|
||||||
return async (dispatch: Dispatch) => {
|
serviceName: string,
|
||||||
let request_string = 'service/overview?service='+serviceName+'&start='+globalTime.minTime+'&end='+globalTime.maxTime+'&step=60';
|
globalTime: GlobalTime,
|
||||||
const response = await metricsAPI.get<metricItem[]>(request_string);
|
) => {
|
||||||
|
return async (dispatch: Dispatch) => {
|
||||||
dispatch<getServiceMetricsAction>({
|
let request_string =
|
||||||
type: ActionTypes.getServiceMetrics,
|
"service/overview?service=" +
|
||||||
payload: response.data
|
serviceName +
|
||||||
//PNOTE - response.data in the axios response has the actual API response
|
"&start=" +
|
||||||
});
|
globalTime.minTime +
|
||||||
};
|
"&end=" +
|
||||||
|
globalTime.maxTime +
|
||||||
|
"&step=60";
|
||||||
|
const response = await metricsAPI.get<metricItem[]>(request_string);
|
||||||
|
|
||||||
|
dispatch<getServiceMetricsAction>({
|
||||||
|
type: ActionTypes.getServiceMetrics,
|
||||||
|
payload: response.data,
|
||||||
|
//PNOTE - response.data in the axios response has the actual API response
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTopEndpoints = (serviceName:string, globalTime: GlobalTime) => {
|
export const getTopEndpoints = (
|
||||||
return async (dispatch: Dispatch) => {
|
serviceName: string,
|
||||||
let request_string = 'service/top_endpoints?service='+serviceName+'&start='+globalTime.minTime+'&end='+globalTime.maxTime;
|
globalTime: GlobalTime,
|
||||||
const response = await metricsAPI.get<topEndpointListItem[]>(request_string);
|
) => {
|
||||||
|
return async (dispatch: Dispatch) => {
|
||||||
dispatch<getTopEndpointsAction>({
|
let request_string =
|
||||||
type: ActionTypes.getTopEndpoints,
|
"service/top_endpoints?service=" +
|
||||||
payload: response.data
|
serviceName +
|
||||||
//PNOTE - response.data in the axios response has the actual API response
|
"&start=" +
|
||||||
});
|
globalTime.minTime +
|
||||||
};
|
"&end=" +
|
||||||
|
globalTime.maxTime;
|
||||||
|
const response = await metricsAPI.get<topEndpointListItem[]>(request_string);
|
||||||
|
|
||||||
|
dispatch<getTopEndpointsAction>({
|
||||||
|
type: ActionTypes.getTopEndpoints,
|
||||||
|
payload: response.data,
|
||||||
|
//PNOTE - response.data in the axios response has the actual API response
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFilteredTraceMetrics = (filter_params: string, globalTime: GlobalTime) => {
|
export const getFilteredTraceMetrics = (
|
||||||
return async (dispatch: Dispatch) => {
|
filter_params: string,
|
||||||
let request_string = 'spans/aggregates?start='+globalTime.minTime+'&end='+globalTime.maxTime+'&'+filter_params;
|
globalTime: GlobalTime,
|
||||||
const response = await metricsAPI.get<customMetricsItem[]>(request_string);
|
) => {
|
||||||
|
return async (dispatch: Dispatch) => {
|
||||||
dispatch<getFilteredTraceMetricsAction>({
|
let request_string =
|
||||||
type: ActionTypes.getFilteredTraceMetrics,
|
"spans/aggregates?start=" +
|
||||||
payload: response.data
|
globalTime.minTime +
|
||||||
//PNOTE - response.data in the axios response has the actual API response
|
"&end=" +
|
||||||
});
|
globalTime.maxTime +
|
||||||
};
|
"&" +
|
||||||
};
|
filter_params;
|
||||||
|
const response = await metricsAPI.get<customMetricsItem[]>(request_string);
|
||||||
|
|
||||||
|
dispatch<getFilteredTraceMetricsAction>({
|
||||||
|
type: ActionTypes.getFilteredTraceMetrics,
|
||||||
|
payload: response.data,
|
||||||
|
//PNOTE - response.data in the axios response has the actual API response
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,49 +1,47 @@
|
|||||||
// Action creator must have a type and optionally a payload
|
// Action creator must have a type and optionally a payload
|
||||||
import { ActionTypes } from './types'
|
import { ActionTypes } from "./types";
|
||||||
|
|
||||||
export interface TagItem {
|
export interface TagItem {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
operator: 'equals'|'contains';
|
operator: "equals" | "contains";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LatencyValue {
|
export interface LatencyValue {
|
||||||
min:string;
|
min: string;
|
||||||
max:string;
|
max: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TraceFilters{
|
export interface TraceFilters {
|
||||||
tags?: TagItem[];
|
tags?: TagItem[];
|
||||||
service?:string;
|
service?: string;
|
||||||
latency?:LatencyValue;
|
latency?: LatencyValue;
|
||||||
operation?:string;
|
operation?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
//define interface for action. Action creator always returns object of this type
|
//define interface for action. Action creator always returns object of this type
|
||||||
export interface updateTraceFiltersAction {
|
export interface updateTraceFiltersAction {
|
||||||
type: ActionTypes.updateTraceFilters,
|
type: ActionTypes.updateTraceFilters;
|
||||||
payload: TraceFilters,
|
payload: TraceFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateTraceFilters = (traceFilters: TraceFilters) => {
|
export const updateTraceFilters = (traceFilters: TraceFilters) => {
|
||||||
return {
|
return {
|
||||||
type: ActionTypes.updateTraceFilters,
|
type: ActionTypes.updateTraceFilters,
|
||||||
payload: traceFilters,
|
payload: traceFilters,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface updateInputTagAction {
|
export interface updateInputTagAction {
|
||||||
type: ActionTypes.updateInput,
|
type: ActionTypes.updateInput;
|
||||||
payload: string,
|
payload: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateInputTag = (Input: string) => {
|
export const updateInputTag = (Input: string) => {
|
||||||
|
return {
|
||||||
return {
|
type: ActionTypes.updateInput,
|
||||||
type: ActionTypes.updateInput,
|
payload: Input,
|
||||||
payload: Input,
|
};
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//named export when you want to export multiple functions from the same file
|
//named export when you want to export multiple functions from the same file
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { ActionTypes } from './types';
|
import { ActionTypes } from "./types";
|
||||||
import tracesAPI from '../api/tracesAPI';
|
import tracesAPI from "../api/tracesAPI";
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from "redux";
|
||||||
import { GlobalTime } from './global';
|
import { GlobalTime } from "./global";
|
||||||
|
|
||||||
|
|
||||||
// PNOTE
|
// PNOTE
|
||||||
// define trace interface - what it should return
|
// define trace interface - what it should return
|
||||||
@ -10,137 +9,145 @@ import { GlobalTime } from './global';
|
|||||||
// Date() - takes number of milliseconds as input, our API takes in microseconds
|
// Date() - takes number of milliseconds as input, our API takes in microseconds
|
||||||
// Sample API call for traces - https://api.signoz.io/api/traces?end=1606968273667000&limit=20&lookback=2d&maxDuration=&minDuration=&service=driver&operation=&start=1606968100867000
|
// Sample API call for traces - https://api.signoz.io/api/traces?end=1606968273667000&limit=20&lookback=2d&maxDuration=&minDuration=&service=driver&operation=&start=1606968100867000
|
||||||
|
|
||||||
|
export interface Tree {
|
||||||
export interface Tree{
|
name: string;
|
||||||
name: string;
|
value: number;
|
||||||
value: number;
|
children?: Tree[];
|
||||||
children?: Tree[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefItem{
|
export interface RefItem {
|
||||||
refType: string;
|
refType: string;
|
||||||
traceID: string;
|
traceID: string;
|
||||||
spanID: string;
|
spanID: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TraceTagItem{
|
export interface TraceTagItem {
|
||||||
key: string;
|
key: string;
|
||||||
// type: string;
|
// type: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessItem{
|
export interface ProcessItem {
|
||||||
serviceName: string;
|
serviceName: string;
|
||||||
tags: TraceTagItem[];
|
tags: TraceTagItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// PNOTE - Temp DS for converting span to tree which can be consumed by flamegraph
|
// PNOTE - Temp DS for converting span to tree which can be consumed by flamegraph
|
||||||
export interface pushDStree {
|
export interface pushDStree {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
time: number;
|
time: number;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
tags: TraceTagItem[];
|
tags: TraceTagItem[];
|
||||||
children: pushDStree[];
|
children: pushDStree[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface spanItem{
|
export interface spanItem {
|
||||||
traceID: string; // index 0
|
traceID: string; // index 0
|
||||||
spanID: string; // index 1
|
spanID: string; // index 1
|
||||||
operationName: string; // index 2
|
operationName: string; // index 2
|
||||||
startTime: number; // index 3
|
startTime: number; // index 3
|
||||||
duration: number; // index 4
|
duration: number; // index 4
|
||||||
references: RefItem[]; // index 5
|
references: RefItem[]; // index 5
|
||||||
tags: []; //index 6
|
tags: []; //index 6
|
||||||
logs: []; // index 7
|
logs: []; // index 7
|
||||||
processID: string; // index 8
|
processID: string; // index 8
|
||||||
warnings: []; // index 9
|
warnings: []; // index 9
|
||||||
children: pushDStree[]; // index 10 // PNOTE - added this as we are adding extra field in span item to convert trace to tree.
|
children: pushDStree[]; // index 10 // PNOTE - added this as we are adding extra field in span item to convert trace to tree.
|
||||||
// Should this field be optional?
|
// Should this field be optional?
|
||||||
}
|
}
|
||||||
|
|
||||||
//let mapped_array :{ [id: string] : spanItem; } = {};
|
//let mapped_array :{ [id: string] : spanItem; } = {};
|
||||||
|
|
||||||
export interface traceItem{
|
export interface traceItem {
|
||||||
traceID: string;
|
traceID: string;
|
||||||
spans: spanItem[];
|
spans: spanItem[];
|
||||||
processes: { [id: string] : ProcessItem; } ;
|
processes: { [id: string]: ProcessItem };
|
||||||
warnings: [];
|
warnings: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface traceResponse{
|
export interface traceResponse {
|
||||||
data: traceItem[];
|
data: traceItem[];
|
||||||
total: number;
|
total: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
error: [];
|
error: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type span = [number, string, string, string, string, string, string, string|string[], string|string[], string|string[], pushDStree[]];
|
export type span = [
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
export interface spanList{
|
string,
|
||||||
|
string,
|
||||||
events: span[];
|
string,
|
||||||
segmentID: string;
|
string,
|
||||||
columns: string[];
|
string | string[],
|
||||||
|
string | string[],
|
||||||
|
string | string[],
|
||||||
|
pushDStree[],
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface spanList {
|
||||||
|
events: span[];
|
||||||
|
segmentID: string;
|
||||||
|
columns: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// export interface traceResponseNew{
|
// export interface traceResponseNew{
|
||||||
// [id: string] : spanList;
|
// [id: string] : spanList;
|
||||||
// }
|
// }
|
||||||
export interface traceResponseNew{
|
export interface traceResponseNew {
|
||||||
[id: string] : spanList;
|
[id: string]: spanList;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface spansWSameTraceIDResponse{
|
export interface spansWSameTraceIDResponse {
|
||||||
[id: string] : spanList;
|
[id: string]: spanList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface FetchTracesAction {
|
export interface FetchTracesAction {
|
||||||
type: ActionTypes.fetchTraces;
|
type: ActionTypes.fetchTraces;
|
||||||
payload: traceResponseNew;
|
payload: traceResponseNew;
|
||||||
}
|
|
||||||
|
|
||||||
export interface FetchTraceItemAction {
|
|
||||||
type: ActionTypes.fetchTraceItem;
|
|
||||||
payload: spansWSameTraceIDResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTraces = (globalTime: GlobalTime, filter_params: string ) => {
|
export interface FetchTraceItemAction {
|
||||||
return async (dispatch: Dispatch) => {
|
type: ActionTypes.fetchTraceItem;
|
||||||
if (globalTime){
|
payload: spansWSameTraceIDResponse;
|
||||||
let request_string = 'spans?limit=100&lookback=2d&start='+globalTime.minTime+'&end='+globalTime.maxTime+'&'+filter_params;
|
}
|
||||||
const response = await tracesAPI.get<traceResponseNew>(request_string);
|
|
||||||
|
|
||||||
dispatch<FetchTracesAction>({
|
|
||||||
type: ActionTypes.fetchTraces,
|
|
||||||
payload: response.data
|
|
||||||
//PNOTE - response.data in the axios response has the actual API response?
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
export const fetchTraces = (globalTime: GlobalTime, filter_params: string) => {
|
||||||
|
return async (dispatch: Dispatch) => {
|
||||||
};
|
if (globalTime) {
|
||||||
};
|
let request_string =
|
||||||
|
"spans?limit=100&lookback=2d&start=" +
|
||||||
export const fetchTraceItem = (traceID: string) => {
|
globalTime.minTime +
|
||||||
return async (dispatch: Dispatch) => {
|
"&end=" +
|
||||||
let request_string = 'traces/'+traceID;
|
globalTime.maxTime +
|
||||||
const response = await tracesAPI.get<spansWSameTraceIDResponse>(request_string);
|
"&" +
|
||||||
|
filter_params;
|
||||||
dispatch<FetchTraceItemAction>({
|
const response = await tracesAPI.get<traceResponseNew>(request_string);
|
||||||
type: ActionTypes.fetchTraceItem,
|
|
||||||
payload: response.data
|
|
||||||
//PNOTE - response.data in the axios response has the actual API response?
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
dispatch<FetchTracesAction>({
|
||||||
|
type: ActionTypes.fetchTraces,
|
||||||
|
payload: response.data,
|
||||||
|
//PNOTE - response.data in the axios response has the actual API response?
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchTraceItem = (traceID: string) => {
|
||||||
|
return async (dispatch: Dispatch) => {
|
||||||
|
let request_string = "traces/" + traceID;
|
||||||
|
const response = await tracesAPI.get<spansWSameTraceIDResponse>(
|
||||||
|
request_string,
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch<FetchTraceItemAction>({
|
||||||
|
type: ActionTypes.fetchTraceItem,
|
||||||
|
payload: response.data,
|
||||||
|
//PNOTE - response.data in the axios response has the actual API response?
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,22 +1,35 @@
|
|||||||
import { FetchTracesAction, FetchTraceItemAction } from './traces';
|
import { FetchTracesAction, FetchTraceItemAction } from "./traces";
|
||||||
import { updateTraceFiltersAction, updateInputTagAction } from './traceFilters';
|
import { updateTraceFiltersAction, updateInputTagAction } from "./traceFilters";
|
||||||
import {getServicesListAction,getServiceMetricsAction, getTopEndpointsAction, getFilteredTraceMetricsAction} from './metrics'
|
import {
|
||||||
import {getUsageDataAction} from './usage'
|
getServicesListAction,
|
||||||
import {updateTimeIntervalAction} from './global';
|
getServiceMetricsAction,
|
||||||
|
getTopEndpointsAction,
|
||||||
|
getFilteredTraceMetricsAction,
|
||||||
|
} from "./metrics";
|
||||||
|
import { getUsageDataAction } from "./usage";
|
||||||
|
import { updateTimeIntervalAction } from "./global";
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
updateTraceFilters,
|
updateTraceFilters,
|
||||||
updateInput,
|
updateInput,
|
||||||
fetchTraces,
|
fetchTraces,
|
||||||
fetchTraceItem,
|
fetchTraceItem,
|
||||||
getServicesList,
|
getServicesList,
|
||||||
getServiceMetrics,
|
getServiceMetrics,
|
||||||
getTopEndpoints,
|
getTopEndpoints,
|
||||||
getUsageData,
|
getUsageData,
|
||||||
updateTimeInterval,
|
updateTimeInterval,
|
||||||
getFilteredTraceMetrics,
|
getFilteredTraceMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Action = FetchTraceItemAction | FetchTracesAction | updateTraceFiltersAction | updateInputTagAction |getServicesListAction| getServiceMetricsAction| getTopEndpointsAction | getUsageDataAction | updateTimeIntervalAction| getFilteredTraceMetricsAction;
|
export type Action =
|
||||||
|
| FetchTraceItemAction
|
||||||
|
| FetchTracesAction
|
||||||
|
| updateTraceFiltersAction
|
||||||
|
| updateInputTagAction
|
||||||
|
| getServicesListAction
|
||||||
|
| getServiceMetricsAction
|
||||||
|
| getTopEndpointsAction
|
||||||
|
| getUsageDataAction
|
||||||
|
| updateTimeIntervalAction
|
||||||
|
| getFilteredTraceMetricsAction;
|
||||||
|
@ -1,29 +1,33 @@
|
|||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from "redux";
|
||||||
import metricsAPI from '../api/metricsAPI';
|
import metricsAPI from "../api/metricsAPI";
|
||||||
import { ActionTypes } from './types';
|
import { ActionTypes } from "./types";
|
||||||
import { GlobalTime } from './global';
|
import { GlobalTime } from "./global";
|
||||||
|
|
||||||
|
export interface usageDataItem {
|
||||||
export interface usageDataItem{
|
timestamp: number;
|
||||||
"timestamp":number;
|
count: number;
|
||||||
"count":number;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export interface getUsageDataAction {
|
export interface getUsageDataAction {
|
||||||
type: ActionTypes.getUsageData;
|
type: ActionTypes.getUsageData;
|
||||||
payload: usageDataItem[];
|
payload: usageDataItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUsageData = (globalTime: GlobalTime) => {
|
export const getUsageData = (globalTime: GlobalTime) => {
|
||||||
return async (dispatch: Dispatch) => {
|
return async (dispatch: Dispatch) => {
|
||||||
let request_string = 'usage?start='+globalTime.minTime+'&end='+globalTime.maxTime+'&step=3600&service=driver';
|
let request_string =
|
||||||
//Step can only be multiple of 3600
|
"usage?start=" +
|
||||||
const response = await metricsAPI.get<usageDataItem[]>(request_string);
|
globalTime.minTime +
|
||||||
|
"&end=" +
|
||||||
dispatch<getUsageDataAction>({
|
globalTime.maxTime +
|
||||||
type: ActionTypes.getUsageData,
|
"&step=3600&service=driver";
|
||||||
payload: response.data
|
//Step can only be multiple of 3600
|
||||||
//PNOTE - response.data in the axios response has the actual API response
|
const response = await metricsAPI.get<usageDataItem[]>(request_string);
|
||||||
});
|
|
||||||
};
|
dispatch<getUsageDataAction>({
|
||||||
|
type: ActionTypes.getUsageData,
|
||||||
|
payload: response.data,
|
||||||
|
//PNOTE - response.data in the axios response has the actual API response
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
|
|
||||||
// No auth for the API
|
// No auth for the API
|
||||||
export default axios.create({
|
export default axios.create({
|
||||||
baseURL: 'https://api.signoz.io/api/prom/api/v1',
|
baseURL: "https://api.signoz.io/api/prom/api/v1",
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
|
|
||||||
export default axios.create({
|
export default axios.create({
|
||||||
// baseURL: 'http://104.211.113.204:8080/api/v1/',
|
// baseURL: 'http://104.211.113.204:8080/api/v1/',
|
||||||
// baseURL: process.env.REACT_APP_QUERY_SERVICE_URL,
|
// baseURL: process.env.REACT_APP_QUERY_SERVICE_URL,
|
||||||
// console.log('in metrics API', process.env.QUERY_SERVICE_URL)
|
// console.log('in metrics API', process.env.QUERY_SERVICE_URL)
|
||||||
baseURL: '/api/v1/',
|
baseURL: "/api/v1/",
|
||||||
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
|
|
||||||
export default axios.create({
|
export default axios.create({
|
||||||
// baseURL: 'https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/',
|
// baseURL: 'https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/',
|
||||||
// baseURL: 'http://104.211.113.204:8080/api/v1/',
|
// baseURL: 'http://104.211.113.204:8080/api/v1/',
|
||||||
baseURL: '/api/v1/',
|
baseURL: "/api/v1/",
|
||||||
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/sendMessage?chat_id=351813222&text=Hello%20there
|
//https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/sendMessage?chat_id=351813222&text=Hello%20there
|
||||||
|
|
||||||
// Chat ID can be obtained from here
|
// Chat ID can be obtained from here
|
||||||
//https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/getUpdates
|
//https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/getUpdates
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
//import { format } from 'path';
|
//import { format } from 'path';
|
||||||
|
|
||||||
export default axios.create({
|
export default axios.create({
|
||||||
// baseURL: 'http://104.211.113.204:8080/api/v1/' //comment this line and remove this comment before pushing
|
// baseURL: 'http://104.211.113.204:8080/api/v1/' //comment this line and remove this comment before pushing
|
||||||
// baseURL: process.env.QUERY_SERVICE_URL,
|
// baseURL: process.env.QUERY_SERVICE_URL,
|
||||||
// console.log('in traces API', process.env.QUERY_SERVICE_URL)
|
// console.log('in traces API', process.env.QUERY_SERVICE_URL)
|
||||||
baseURL: '/api/v1/',
|
baseURL: "/api/v1/",
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,122 +1,145 @@
|
|||||||
import React, { Suspense, useState } from 'react';
|
import React, { Suspense, useState } from "react";
|
||||||
import { Layout, Menu, Switch as ToggleButton, Spin, Row, Col } from 'antd';
|
import { Layout, Menu, Switch as ToggleButton, Spin, Row, Col } from "antd";
|
||||||
import { useThemeSwitcher } from "react-css-theme-switcher";
|
import { useThemeSwitcher } from "react-css-theme-switcher";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
|
||||||
import {
|
import {
|
||||||
|
NavLink,
|
||||||
LineChartOutlined,
|
BrowserRouter as Router,
|
||||||
BarChartOutlined,
|
Route,
|
||||||
DeploymentUnitOutlined,
|
Switch,
|
||||||
AlignLeftOutlined,
|
} from "react-router-dom";
|
||||||
} from '@ant-design/icons';
|
import {
|
||||||
|
LineChartOutlined,
|
||||||
|
BarChartOutlined,
|
||||||
|
DeploymentUnitOutlined,
|
||||||
import DateTimeSelector from './DateTimeSelector';
|
AlignLeftOutlined,
|
||||||
import ShowBreadcrumbs from './ShowBreadcrumbs';
|
} from "@ant-design/icons";
|
||||||
|
|
||||||
|
import DateTimeSelector from "./DateTimeSelector";
|
||||||
|
import ShowBreadcrumbs from "./ShowBreadcrumbs";
|
||||||
|
|
||||||
const { Content, Footer, Sider } = Layout;
|
const { Content, Footer, Sider } = Layout;
|
||||||
|
|
||||||
const ServiceMetrics = React.lazy(() => import('./metrics/ServiceMetricsDef'));
|
const ServiceMetrics = React.lazy(() => import("./metrics/ServiceMetricsDef"));
|
||||||
const ServiceMap = React.lazy(() => import('./servicemap/ServiceMap'));
|
const ServiceMap = React.lazy(() => import("./servicemap/ServiceMap"));
|
||||||
const TraceDetail = React.lazy(() => import('./traces/TraceDetail'));
|
const TraceDetail = React.lazy(() => import("./traces/TraceDetail"));
|
||||||
const TraceGraph = React.lazy(() => import ('./traces/TraceGraphDef' ));
|
const TraceGraph = React.lazy(() => import("./traces/TraceGraphDef"));
|
||||||
const UsageExplorer = React.lazy(() => import ('./usage/UsageExplorerDef' ));
|
const UsageExplorer = React.lazy(() => import("./usage/UsageExplorerDef"));
|
||||||
const ServicesTable = React.lazy(() => import('./metrics/ServicesTableDef'));
|
const ServicesTable = React.lazy(() => import("./metrics/ServicesTableDef"));
|
||||||
// const Signup = React.lazy(() => import('./Signup'));
|
// const Signup = React.lazy(() => import('./Signup'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//PNOTE
|
//PNOTE
|
||||||
//React. lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don't pull in unused components.
|
//React. lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don't pull in unused components.
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
// state = { collapsed: false, isDarkMode: true };
|
// state = { collapsed: false, isDarkMode: true };
|
||||||
const { switcher, currentTheme, status, themes } = useThemeSwitcher();
|
const { switcher, currentTheme, status, themes } = useThemeSwitcher();
|
||||||
const [isDarkMode, setIsDarkMode] = useState(true);
|
const [isDarkMode, setIsDarkMode] = useState(true);
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
const toggleTheme = (isChecked :boolean) => {
|
const toggleTheme = (isChecked: boolean) => {
|
||||||
setIsDarkMode(isChecked);
|
setIsDarkMode(isChecked);
|
||||||
switcher({ theme: isChecked ? themes.dark : themes.light });
|
switcher({ theme: isChecked ? themes.dark : themes.light });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onCollapse = (): void => {
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
};
|
||||||
|
|
||||||
const onCollapse = (): void => {
|
if (status === "loading") {
|
||||||
setCollapsed(!collapsed);
|
return null;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (status === "loading") {
|
return (
|
||||||
return null;
|
<Router basename="/">
|
||||||
}
|
<Layout style={{ minHeight: "100vh" }}>
|
||||||
|
<Sider
|
||||||
return (
|
collapsible
|
||||||
<Router basename="/">
|
collapsed={collapsed}
|
||||||
|
onCollapse={onCollapse}
|
||||||
|
width={160}
|
||||||
|
>
|
||||||
|
<div className="logo">
|
||||||
|
<ToggleButton checked={isDarkMode} onChange={toggleTheme} />
|
||||||
|
<NavLink to="/">
|
||||||
|
<img
|
||||||
|
src={"signoz.svg"}
|
||||||
|
alt={"SigNoz"}
|
||||||
|
style={{
|
||||||
|
margin: "5%",
|
||||||
|
width: 100,
|
||||||
|
display: !collapsed ? "block" : "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Layout style={{ minHeight: '100vh' }}>
|
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
|
||||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={160}>
|
<Menu.Item key="1" icon={<BarChartOutlined />}>
|
||||||
<div className="logo">
|
<NavLink
|
||||||
<ToggleButton checked={isDarkMode} onChange={toggleTheme} />
|
to="/application"
|
||||||
<NavLink to='/'><img src={"signoz.svg"} alt={'SigNoz'} style={{margin: '5%', width: 100, display: !collapsed ? 'block' : 'none'}} /></NavLink>
|
style={{ fontSize: 12, textDecoration: "none" }}
|
||||||
</div>
|
>
|
||||||
|
Metrics
|
||||||
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline">
|
</NavLink>
|
||||||
<Menu.Item key="1" icon={<BarChartOutlined />}>
|
</Menu.Item>
|
||||||
<NavLink to='/application' style={{fontSize: 12, textDecoration: 'none'}}>Metrics</NavLink>
|
<Menu.Item key="2" icon={<AlignLeftOutlined />}>
|
||||||
</Menu.Item>
|
<NavLink to="/traces" style={{ fontSize: 12, textDecoration: "none" }}>
|
||||||
<Menu.Item key="2" icon={<AlignLeftOutlined />}>
|
Traces
|
||||||
<NavLink to='/traces' style={{fontSize: 12, textDecoration: 'none'}}>Traces</NavLink>
|
</NavLink>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="3" icon={<DeploymentUnitOutlined />}>
|
<Menu.Item key="3" icon={<DeploymentUnitOutlined />}>
|
||||||
<NavLink to='/service-map' style={{fontSize: 12, textDecoration: 'none'}}>Service Map</NavLink>
|
<NavLink
|
||||||
</Menu.Item>
|
to="/service-map"
|
||||||
<Menu.Item key="4" icon={<LineChartOutlined />}>
|
style={{ fontSize: 12, textDecoration: "none" }}
|
||||||
<NavLink to='/usage-explorer' style={{fontSize: 12, textDecoration: 'none'}}>Usage Explorer</NavLink>
|
>
|
||||||
</Menu.Item>
|
Service Map
|
||||||
</Menu>
|
</NavLink>
|
||||||
</Sider>
|
</Menu.Item>
|
||||||
<Layout className="site-layout">
|
<Menu.Item key="4" icon={<LineChartOutlined />}>
|
||||||
|
<NavLink
|
||||||
|
to="/usage-explorer"
|
||||||
|
style={{ fontSize: 12, textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
Usage Explorer
|
||||||
|
</NavLink>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
</Sider>
|
||||||
|
<Layout className="site-layout">
|
||||||
|
<Content style={{ margin: "0 16px" }}>
|
||||||
|
<Row>
|
||||||
|
<Col span={20}>
|
||||||
|
<ShowBreadcrumbs />
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Content style={{ margin: '0 16px' }}>
|
<Col span={4}>
|
||||||
<Row>
|
<DateTimeSelector />
|
||||||
<Col span={20}>
|
</Col>
|
||||||
<ShowBreadcrumbs />
|
</Row>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col span={4}>
|
{/* <Divider /> */}
|
||||||
<DateTimeSelector />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{/* <Divider /> */}
|
<Suspense fallback={<Spin size="large" />}>
|
||||||
|
<Switch>
|
||||||
<Suspense fallback={<Spin size="large" />}>
|
<Route path="/application/:servicename" component={ServiceMetrics} />
|
||||||
<Switch>
|
<Route path="/service-map" component={ServiceMap} />
|
||||||
<Route path="/application/:servicename" component={ServiceMetrics}/>
|
<Route path="/traces" exact component={TraceDetail} />
|
||||||
<Route path="/service-map" component={ServiceMap}/>
|
<Route path="/traces/:id" component={TraceGraph} />
|
||||||
<Route path="/traces" exact component={TraceDetail}/>
|
<Route path="/usage-explorer" component={UsageExplorer} />
|
||||||
<Route path="/traces/:id" component={TraceGraph}/>
|
<Route path="/" component={ServicesTable} />
|
||||||
<Route path="/usage-explorer" component={UsageExplorer}/>
|
<Route path="/application" exact component={ServicesTable} />
|
||||||
<Route path="/" component={ServicesTable}/>
|
{/* <Route path="/signup" component={Signup} /> */}
|
||||||
<Route path="/application" exact component={ServicesTable}/>
|
</Switch>
|
||||||
{/* <Route path="/signup" component={Signup} /> */}
|
</Suspense>
|
||||||
|
</Content>
|
||||||
</Switch>
|
<Footer style={{ textAlign: "center", fontSize: 10 }}>
|
||||||
</Suspense>
|
SigNoz Inc. ©2020{" "}
|
||||||
|
</Footer>
|
||||||
|
</Layout>
|
||||||
</Content>
|
</Layout>
|
||||||
<Footer style={{ textAlign: 'center', fontSize: 10 }}>SigNoz Inc. ©2020 </Footer>
|
</Router>
|
||||||
</Layout>
|
);
|
||||||
</Layout>
|
};
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
@ -1,53 +1,46 @@
|
|||||||
import React,{Suspense, useState} from 'react';
|
import React, { Suspense, useState } from "react";
|
||||||
import {Spin} from 'antd';
|
import { Spin } from "antd";
|
||||||
|
|
||||||
|
import {
|
||||||
|
BrowserRouter as Router,
|
||||||
|
Route,
|
||||||
|
Switch,
|
||||||
|
Redirect,
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
|
const Signup = React.lazy(() => import("./Signup"));
|
||||||
|
const App = React.lazy(() => import("./App"));
|
||||||
|
|
||||||
|
const AppWrapper = () => {
|
||||||
|
const [isUserAuthenticated, setIsUserAuthenticated] = useState(false);
|
||||||
|
|
||||||
const Signup = React.lazy(() => import('./Signup'));
|
return (
|
||||||
const App = React.lazy(() => import('./App'));
|
<Router basename="/">
|
||||||
|
<Suspense fallback={<Spin size="large" />}>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/signup" exact component={Signup} />
|
||||||
|
<Route path="/application" exact component={App} />
|
||||||
|
<Route path="/application/:servicename" component={App} />
|
||||||
|
<Route path="/service-map" component={App} />
|
||||||
|
<Route path="/traces" exact component={App} />
|
||||||
|
<Route path="/traces/:id" component={App} />
|
||||||
|
<Route path="/usage-explorer" component={App} />
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
exact
|
||||||
|
render={() => {
|
||||||
|
return localStorage.getItem("isLoggedIn") === "yes" ? (
|
||||||
|
<Redirect to="/application" />
|
||||||
|
) : (
|
||||||
|
<Redirect to="/signup" />
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</Suspense>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppWrapper;
|
||||||
const AppWrapper = () => {
|
|
||||||
|
|
||||||
const [isUserAuthenticated, setIsUserAuthenticated] = useState(false);
|
|
||||||
|
|
||||||
return(
|
|
||||||
|
|
||||||
<Router basename="/">
|
|
||||||
|
|
||||||
<Suspense fallback={<Spin size="large" />}>
|
|
||||||
<Switch>
|
|
||||||
<Route path="/signup" exact component={Signup} />
|
|
||||||
<Route path="/application" exact component={App} />
|
|
||||||
<Route path="/application/:servicename" component={App}/>
|
|
||||||
<Route path="/service-map" component={App}/>
|
|
||||||
<Route path="/traces" exact component={App}/>
|
|
||||||
<Route path="/traces/:id" component={App}/>
|
|
||||||
<Route path="/usage-explorer" component={App}/>
|
|
||||||
|
|
||||||
<Route path="/" exact
|
|
||||||
render={() => {
|
|
||||||
return (
|
|
||||||
localStorage.getItem('isLoggedIn')==='yes' ?
|
|
||||||
<Redirect to="/application" /> :
|
|
||||||
<Redirect to="/signup" />
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</Switch>
|
|
||||||
</Suspense>
|
|
||||||
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AppWrapper;
|
|
||||||
|
@ -1,56 +1,58 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { Modal, DatePicker} from 'antd';
|
import { Modal, DatePicker } from "antd";
|
||||||
import {DateTimeRangeType} from '../actions'
|
import { DateTimeRangeType } from "../actions";
|
||||||
import { Moment } from 'moment'
|
import { Moment } from "moment";
|
||||||
import moment from 'moment';
|
import moment from "moment";
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
interface CustomDateTimeModalProps {
|
interface CustomDateTimeModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onCreate: (dateTimeRange: DateTimeRangeType) => void; //Store is defined in antd forms library
|
onCreate: (dateTimeRange: DateTimeRangeType) => void; //Store is defined in antd forms library
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({
|
||||||
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({ //destructuring props
|
//destructuring props
|
||||||
visible,
|
visible,
|
||||||
onCreate,
|
onCreate,
|
||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
}) => {
|
||||||
|
// RangeValue<Moment> == [Moment|null,Moment|null]|null
|
||||||
|
|
||||||
// RangeValue<Moment> == [Moment|null,Moment|null]|null
|
const [
|
||||||
|
customDateTimeRange,
|
||||||
|
setCustomDateTimeRange,
|
||||||
|
] = useState<DateTimeRangeType>();
|
||||||
|
|
||||||
const [customDateTimeRange, setCustomDateTimeRange]=useState<DateTimeRangeType>();
|
function handleRangePickerOk(date_time: DateTimeRangeType) {
|
||||||
|
setCustomDateTimeRange(date_time);
|
||||||
|
}
|
||||||
|
function disabledDate(current: Moment) {
|
||||||
|
if (current > moment()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleRangePickerOk(date_time: DateTimeRangeType) {
|
return (
|
||||||
setCustomDateTimeRange(date_time);
|
<Modal
|
||||||
}
|
visible={visible}
|
||||||
function disabledDate(current:Moment){
|
title="Chose date and time range"
|
||||||
|
okText="Apply"
|
||||||
if (current > moment()){
|
cancelText="Cancel"
|
||||||
return true;
|
onCancel={onCancel}
|
||||||
}
|
style={{ position: "absolute", top: 60, right: 40 }}
|
||||||
else {
|
onOk={() => onCreate(customDateTimeRange ? customDateTimeRange : null)}
|
||||||
return false;
|
>
|
||||||
}
|
<RangePicker
|
||||||
}
|
disabledDate={disabledDate}
|
||||||
|
onOk={handleRangePickerOk}
|
||||||
return (
|
showTime
|
||||||
<Modal
|
/>
|
||||||
visible={visible}
|
</Modal>
|
||||||
title="Chose date and time range"
|
);
|
||||||
okText="Apply"
|
|
||||||
cancelText="Cancel"
|
|
||||||
onCancel={onCancel}
|
|
||||||
style={{ position: "absolute", top: 60, right: 40 }}
|
|
||||||
onOk={() => onCreate(customDateTimeRange?customDateTimeRange:null)}
|
|
||||||
>
|
|
||||||
|
|
||||||
<RangePicker disabledDate={disabledDate} onOk={handleRangePickerOk} showTime />
|
|
||||||
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CustomDateTimeModal;
|
export default CustomDateTimeModal;
|
||||||
|
@ -1,140 +1,142 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import {Select, Button,Space, Form} from 'antd';
|
import { Select, Button, Space, Form } from "antd";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import { withRouter } from "react-router";
|
import { withRouter } from "react-router";
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import CustomDateTimeModal from './CustomDateTimeModal';
|
|
||||||
import { GlobalTime, updateTimeInterval } from '../actions';
|
|
||||||
import { StoreState } from '../reducers';
|
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
|
|
||||||
import {DateTimeRangeType} from '../actions'
|
|
||||||
|
|
||||||
|
import CustomDateTimeModal from "./CustomDateTimeModal";
|
||||||
|
import { GlobalTime, updateTimeInterval } from "../actions";
|
||||||
|
import { StoreState } from "../reducers";
|
||||||
|
import FormItem from "antd/lib/form/FormItem";
|
||||||
|
|
||||||
|
import { DateTimeRangeType } from "../actions";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const DateTimeWrapper = styled.div`
|
const DateTimeWrapper = styled.div`
|
||||||
margin-top:20px;
|
margin-top: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface DateTimeSelectorProps extends RouteComponentProps<any> {
|
interface DateTimeSelectorProps extends RouteComponentProps<any> {
|
||||||
currentpath?:string;
|
currentpath?: string;
|
||||||
updateTimeInterval: Function;
|
updateTimeInterval: Function;
|
||||||
globalTime: GlobalTime;
|
globalTime: GlobalTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _DateTimeSelector = (props: DateTimeSelectorProps) => {
|
||||||
|
const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false);
|
||||||
|
const [timeInterval, setTimeInterval] = useState("15min");
|
||||||
|
const [refreshButtonHidden, setRefreshButtonHidden] = useState(false);
|
||||||
|
|
||||||
const _DateTimeSelector = (props:DateTimeSelectorProps) => {
|
const [form_dtselector] = Form.useForm();
|
||||||
|
|
||||||
const [customDTPickerVisible,setCustomDTPickerVisible]=useState(false);
|
|
||||||
const [timeInterval,setTimeInterval]=useState('15min')
|
|
||||||
const [refreshButtonHidden, setRefreshButtonHidden]=useState(false)
|
|
||||||
|
|
||||||
const [form_dtselector] = Form.useForm();
|
const handleOnSelect = (value: string) => {
|
||||||
|
if (value === "custom") {
|
||||||
|
setCustomDTPickerVisible(true);
|
||||||
|
} else {
|
||||||
|
props.history.push({
|
||||||
|
search: "?time=" + value,
|
||||||
|
}); //pass time in URL query param for all choices except custom in datetime picker
|
||||||
|
props.updateTimeInterval(value);
|
||||||
|
setTimeInterval(value);
|
||||||
|
setRefreshButtonHidden(false); // for normal intervals, show refresh button
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//function called on clicking apply in customDateTimeModal
|
||||||
|
const handleOk = (dateTimeRange: DateTimeRangeType) => {
|
||||||
|
// pass values in ms [minTime, maxTime]
|
||||||
|
if (
|
||||||
|
dateTimeRange !== null &&
|
||||||
|
dateTimeRange !== undefined &&
|
||||||
|
dateTimeRange[0] !== null &&
|
||||||
|
dateTimeRange[1] !== null
|
||||||
|
) {
|
||||||
|
props.updateTimeInterval("custom", [
|
||||||
|
dateTimeRange[0].valueOf(),
|
||||||
|
dateTimeRange[1].valueOf(),
|
||||||
|
]);
|
||||||
|
//setting globaltime
|
||||||
|
setRefreshButtonHidden(true);
|
||||||
|
form_dtselector.setFieldsValue({
|
||||||
|
interval:
|
||||||
|
dateTimeRange[0].format("YYYY/MM/DD HH:mm") +
|
||||||
|
"-" +
|
||||||
|
dateTimeRange[1].format("YYYY/MM/DD HH:mm"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setCustomDTPickerVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleOnSelect = (value:string) =>
|
const timeSinceLastRefresh = () => {
|
||||||
{
|
let timeDiffSec = Math.round(
|
||||||
if (value === 'custom')
|
(Date.now() - Math.round(props.globalTime.maxTime / 1000000)) / 1000,
|
||||||
{
|
);
|
||||||
setCustomDTPickerVisible(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
props.history.push({
|
|
||||||
search: '?time='+value,
|
|
||||||
}) //pass time in URL query param for all choices except custom in datetime picker
|
|
||||||
props.updateTimeInterval(value);
|
|
||||||
setTimeInterval(value);
|
|
||||||
setRefreshButtonHidden(false); // for normal intervals, show refresh button
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//function called on clicking apply in customDateTimeModal
|
//How will Refresh button get updated? Needs to be periodically updated via timer.
|
||||||
const handleOk = (dateTimeRange:DateTimeRangeType) =>
|
// For now, not returning any text here
|
||||||
{
|
// if (timeDiffSec < 60)
|
||||||
// pass values in ms [minTime, maxTime]
|
// return timeDiffSec.toString()+' s';
|
||||||
if (dateTimeRange!== null && dateTimeRange!== undefined && dateTimeRange[0]!== null && dateTimeRange[1]!== null )
|
// else if (timeDiffSec < 3600)
|
||||||
{
|
// return Math.round(timeDiffSec/60).toString()+' min';
|
||||||
props.updateTimeInterval('custom',[dateTimeRange[0].valueOf(),dateTimeRange[1].valueOf()])
|
// else
|
||||||
//setting globaltime
|
// return Math.round(timeDiffSec/3600).toString()+' hr';
|
||||||
setRefreshButtonHidden(true);
|
return null;
|
||||||
form_dtselector.setFieldsValue({interval:(dateTimeRange[0].format("YYYY/MM/DD HH:mm")+'-'+dateTimeRange[1].format("YYYY/MM/DD HH:mm")) ,})
|
};
|
||||||
}
|
|
||||||
setCustomDTPickerVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeSinceLastRefresh = () => {
|
const handleRefresh = () => {
|
||||||
let timeDiffSec = Math.round((Date.now() - Math.round(props.globalTime.maxTime/1000000))/1000);
|
props.updateTimeInterval(timeInterval);
|
||||||
|
};
|
||||||
|
if (props.location.pathname.startsWith("/usage-explorer")) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<DateTimeWrapper>
|
||||||
|
<Space>
|
||||||
|
<Form
|
||||||
|
form={form_dtselector}
|
||||||
|
layout="inline"
|
||||||
|
initialValues={{ interval: "15min" }}
|
||||||
|
style={{ marginTop: 10, marginBottom: 10 }}
|
||||||
|
>
|
||||||
|
<FormItem name="interval">
|
||||||
|
<Select onSelect={handleOnSelect}>
|
||||||
|
<Option value="custom">Custom</Option>
|
||||||
|
<Option value="15min">Last 15 min</Option>
|
||||||
|
<Option value="30min">Last 30 min</Option>
|
||||||
|
<Option value="1hr">Last 1 hour</Option>
|
||||||
|
<Option value="6hr">Last 6 hour</Option>
|
||||||
|
<Option value="1day">Last 1 day</Option>
|
||||||
|
<Option value="1week">Last 1 week</Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
//How will Refresh button get updated? Needs to be periodically updated via timer.
|
<FormItem hidden={refreshButtonHidden} name="refresh_button">
|
||||||
// For now, not returning any text here
|
<Button type="primary" onClick={handleRefresh}>
|
||||||
// if (timeDiffSec < 60)
|
Refresh {timeSinceLastRefresh()}
|
||||||
// return timeDiffSec.toString()+' s';
|
</Button>
|
||||||
// else if (timeDiffSec < 3600)
|
{/* if refresh time is more than x min, give a message? */}
|
||||||
// return Math.round(timeDiffSec/60).toString()+' min';
|
</FormItem>
|
||||||
// else
|
</Form>
|
||||||
// return Math.round(timeDiffSec/3600).toString()+' hr';
|
<CustomDateTimeModal
|
||||||
return null;
|
visible={customDTPickerVisible}
|
||||||
|
onCreate={handleOk}
|
||||||
|
onCancel={() => {
|
||||||
|
setCustomDTPickerVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</DateTimeWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mapStateToProps = (state: StoreState): { globalTime: GlobalTime } => {
|
||||||
|
return { globalTime: state.globalTime };
|
||||||
|
};
|
||||||
|
|
||||||
}
|
export const DateTimeSelector = connect(mapStateToProps, {
|
||||||
|
updateTimeInterval: updateTimeInterval,
|
||||||
|
})(_DateTimeSelector);
|
||||||
|
|
||||||
const handleRefresh = () =>
|
export default withRouter(DateTimeSelector);
|
||||||
{
|
|
||||||
props.updateTimeInterval(timeInterval);
|
|
||||||
}
|
|
||||||
if (props.location.pathname.startsWith('/usage-explorer')) {
|
|
||||||
return null;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
|
|
||||||
<DateTimeWrapper>
|
|
||||||
<Space>
|
|
||||||
<Form form={form_dtselector} layout='inline' initialValues={{ interval:'15min', }} style={{marginTop: 10, marginBottom:10}}>
|
|
||||||
<FormItem name='interval'>
|
|
||||||
<Select onSelect={handleOnSelect} >
|
|
||||||
<Option value="custom">Custom</Option>
|
|
||||||
<Option value="15min">Last 15 min</Option>
|
|
||||||
<Option value="30min">Last 30 min</Option>
|
|
||||||
<Option value="1hr">Last 1 hour</Option>
|
|
||||||
<Option value="6hr">Last 6 hour</Option>
|
|
||||||
<Option value="1day">Last 1 day</Option>
|
|
||||||
<Option value="1week">Last 1 week</Option>
|
|
||||||
</Select>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<FormItem hidden={refreshButtonHidden} name='refresh_button'>
|
|
||||||
|
|
||||||
<Button type="primary" onClick={handleRefresh}>Refresh {timeSinceLastRefresh()}</Button>
|
|
||||||
{/* if refresh time is more than x min, give a message? */}
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
<CustomDateTimeModal
|
|
||||||
visible={customDTPickerVisible}
|
|
||||||
onCreate={handleOk}
|
|
||||||
onCancel={() => {
|
|
||||||
setCustomDTPickerVisible(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</DateTimeWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const mapStateToProps = (state: StoreState ): { globalTime: GlobalTime} => {
|
|
||||||
|
|
||||||
return { globalTime : state.globalTime };
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const DateTimeSelector = connect(mapStateToProps, {
|
|
||||||
updateTimeInterval: updateTimeInterval,
|
|
||||||
|
|
||||||
})(_DateTimeSelector);
|
|
||||||
|
|
||||||
export default withRouter(DateTimeSelector);
|
|
||||||
|
@ -1,57 +1,50 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import {Breadcrumb} from 'antd';
|
import { Breadcrumb } from "antd";
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link, withRouter } from "react-router-dom";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
const BreadCrumbWrapper = styled.div`
|
const BreadCrumbWrapper = styled.div`
|
||||||
padding-top:20px;
|
padding-top: 20px;
|
||||||
padding-left:20px;
|
padding-left: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const breadcrumbNameMap: any = {
|
||||||
const breadcrumbNameMap :any = { // PNOTE - TO DO - Remove any and do typechecking - like https://stackoverflow.com/questions/56568423/typescript-no-index-signature-with-a-parameter-of-type-string-was-found-on-ty
|
// PNOTE - TO DO - Remove any and do typechecking - like https://stackoverflow.com/questions/56568423/typescript-no-index-signature-with-a-parameter-of-type-string-was-found-on-ty
|
||||||
'/application': 'Application',
|
"/application": "Application",
|
||||||
'/traces': 'Traces',
|
"/traces": "Traces",
|
||||||
'/service-map': 'Service Map',
|
"/service-map": "Service Map",
|
||||||
'/usage-explorer': 'Usage Explorer',
|
"/usage-explorer": "Usage Explorer",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ShowBreadcrumbs = withRouter((props) => {
|
||||||
|
const { location } = props;
|
||||||
|
const pathSnippets = location.pathname.split("/").filter((i) => i);
|
||||||
|
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
|
||||||
|
const url = `/${pathSnippets.slice(0, index + 1).join("/")}`;
|
||||||
|
if (breadcrumbNameMap[url] === undefined) {
|
||||||
|
return (
|
||||||
|
<Breadcrumb.Item key={url}>
|
||||||
|
<Link to={url}>{url.split("/").slice(-1)[0]}</Link>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Breadcrumb.Item key={url}>
|
||||||
|
<Link to={url}>{breadcrumbNameMap[url]}</Link>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const breadcrumbItems = [
|
||||||
|
<Breadcrumb.Item key="home">
|
||||||
|
<Link to="/">Home</Link>
|
||||||
|
</Breadcrumb.Item>,
|
||||||
|
].concat(extraBreadcrumbItems);
|
||||||
|
return (
|
||||||
|
<BreadCrumbWrapper>
|
||||||
|
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
|
||||||
|
</BreadCrumbWrapper>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const ShowBreadcrumbs = withRouter(props => {
|
export default ShowBreadcrumbs;
|
||||||
const { location } = props;
|
|
||||||
const pathSnippets = location.pathname.split('/').filter(i => i);
|
|
||||||
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
|
|
||||||
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
|
|
||||||
if (breadcrumbNameMap[url] === undefined){
|
|
||||||
return (
|
|
||||||
<Breadcrumb.Item key={url}>
|
|
||||||
<Link to={url}>{url.split('/').slice(-1)[0]}</Link>
|
|
||||||
</Breadcrumb.Item>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<Breadcrumb.Item key={url}>
|
|
||||||
<Link to={url}>{breadcrumbNameMap[url]}</Link>
|
|
||||||
</Breadcrumb.Item>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
const breadcrumbItems = [
|
|
||||||
<Breadcrumb.Item key="home">
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
</Breadcrumb.Item>,
|
|
||||||
].concat(extraBreadcrumbItems);
|
|
||||||
return (
|
|
||||||
|
|
||||||
<BreadCrumbWrapper>
|
|
||||||
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
|
|
||||||
</BreadCrumbWrapper>
|
|
||||||
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ShowBreadcrumbs;
|
|
||||||
|
@ -1,219 +1,238 @@
|
|||||||
import React, { useState,useRef, Suspense } from 'react';
|
import React, { useState, useRef, Suspense } from "react";
|
||||||
import { Row, Space, Button, Input, Checkbox } from 'antd'
|
import { Row, Space, Button, Input, Checkbox } from "antd";
|
||||||
import submitForm from '../api/submitForm';
|
import submitForm from "../api/submitForm";
|
||||||
import { withRouter } from "react-router";
|
import { withRouter } from "react-router";
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
|
|
||||||
|
interface SignUpProps extends RouteComponentProps<any> {}
|
||||||
|
|
||||||
interface SignUpProps extends RouteComponentProps<any> {
|
const Signup = (props: SignUpProps) => {
|
||||||
|
const [state, setState] = useState({ submitted: false });
|
||||||
}
|
const [formState, setFormState] = useState({
|
||||||
|
firstName: { value: "" },
|
||||||
|
companyName: { value: "" },
|
||||||
|
email: { value: "" },
|
||||||
|
password: { value: "", valid: true },
|
||||||
|
emailOptIn: { value: true },
|
||||||
|
});
|
||||||
|
const passwordInput = useRef(null);
|
||||||
|
// const { createAccount } = useActions(signupLogic)
|
||||||
|
// const { accountLoading } = useValues(signupLogic)
|
||||||
|
// const { plan } = fromParams()
|
||||||
|
|
||||||
const Signup = (props:SignUpProps) => {
|
const updateForm = (name: any, target: any, valueAttr = "value") => {
|
||||||
|
/* Validate password (if applicable) */
|
||||||
|
if (name === "password") {
|
||||||
|
let password = target[valueAttr];
|
||||||
|
const valid = password.length >= 8;
|
||||||
|
setFormState({
|
||||||
|
...formState,
|
||||||
|
password: { ...formState.password, valid, value: target[valueAttr] },
|
||||||
|
});
|
||||||
|
} else if (name === "firstName") {
|
||||||
|
setFormState({
|
||||||
|
...formState,
|
||||||
|
firstName: { ...formState.firstName, value: target[valueAttr] },
|
||||||
|
});
|
||||||
|
} else if (name === "companyName") {
|
||||||
|
setFormState({
|
||||||
|
...formState,
|
||||||
|
companyName: { ...formState.companyName, value: target[valueAttr] },
|
||||||
|
});
|
||||||
|
} else if (name === "email") {
|
||||||
|
setFormState({
|
||||||
|
...formState,
|
||||||
|
email: { ...formState.email, value: target[valueAttr] },
|
||||||
|
});
|
||||||
|
} else if (name === "emailOptIn") {
|
||||||
|
setFormState({
|
||||||
|
...formState,
|
||||||
|
emailOptIn: { ...formState.emailOptIn, value: target[valueAttr] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const [state, setState] = useState({ submitted: false })
|
const handleSubmit = (e: any) => {
|
||||||
const [formState, setFormState] = useState({
|
e.preventDefault();
|
||||||
firstName: {value:''},
|
|
||||||
companyName: {value:''},
|
|
||||||
email: {value:''},
|
|
||||||
password: {value:'',valid:true},
|
|
||||||
emailOptIn: { value: true },
|
|
||||||
})
|
|
||||||
const passwordInput = useRef(null)
|
|
||||||
// const { createAccount } = useActions(signupLogic)
|
|
||||||
// const { accountLoading } = useValues(signupLogic)
|
|
||||||
// const { plan } = fromParams()
|
|
||||||
|
|
||||||
const updateForm = (name:any, target:any, valueAttr = 'value') => {
|
console.log("in handle submit");
|
||||||
/* Validate password (if applicable) */
|
|
||||||
if (name === 'password') {
|
|
||||||
let password = target[valueAttr]
|
|
||||||
const valid = password.length >= 8
|
|
||||||
setFormState({ ...formState, password: { ...formState.password, valid, value: target[valueAttr] } })
|
|
||||||
} else
|
|
||||||
if (name === 'firstName') {
|
|
||||||
|
|
||||||
setFormState({ ...formState, firstName: { ...formState.firstName, value: target[valueAttr] } })
|
|
||||||
} else
|
|
||||||
if (name === 'companyName') {
|
|
||||||
|
|
||||||
setFormState({ ...formState, companyName: { ...formState.companyName, value: target[valueAttr] } })
|
|
||||||
} else
|
|
||||||
if (name === 'email') {
|
|
||||||
|
|
||||||
setFormState({ ...formState, email: { ...formState.email, value: target[valueAttr] } })
|
|
||||||
} else
|
|
||||||
if (name === 'emailOptIn') {
|
|
||||||
|
|
||||||
setFormState({ ...formState, emailOptIn: { ...formState.emailOptIn, value: target[valueAttr] } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = (e:any) => {
|
setState({ ...state, submitted: true });
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
console.log('in handle submit');
|
|
||||||
|
|
||||||
setState({ ...state, submitted: true })
|
/* Password has custom validation */
|
||||||
|
if (!formState.password.valid) {
|
||||||
|
// if (passwordInput.current){
|
||||||
|
// passwordInput.current.focus()
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
first_name: formState.firstName,
|
||||||
|
company_name: formState.companyName || undefined,
|
||||||
|
email: formState.email,
|
||||||
|
password: formState.password,
|
||||||
|
email_opt_in: formState.emailOptIn.value,
|
||||||
|
// plan, // Pass it along if on QS, won't have any effect unless on multitenancy
|
||||||
|
};
|
||||||
|
// createAccount(payload)
|
||||||
|
|
||||||
/* Password has custom validation */
|
// axios.get(`https://jsonplaceholder.typicode.com/users`)
|
||||||
if (!formState.password.valid) {
|
// .then(res => {
|
||||||
// if (passwordInput.current){
|
// console.log(res);
|
||||||
// passwordInput.current.focus()
|
// console.log(res.data);
|
||||||
// }
|
// })
|
||||||
|
|
||||||
// return
|
|
||||||
}
|
|
||||||
const payload = {
|
|
||||||
first_name: formState.firstName,
|
|
||||||
company_name: formState.companyName || undefined,
|
|
||||||
email: formState.email,
|
|
||||||
password: formState.password,
|
|
||||||
email_opt_in: formState.emailOptIn.value,
|
|
||||||
// plan, // Pass it along if on QS, won't have any effect unless on multitenancy
|
|
||||||
}
|
|
||||||
// createAccount(payload)
|
|
||||||
|
|
||||||
|
|
||||||
|
let texttolog = JSON.stringify(payload);
|
||||||
|
|
||||||
// axios.get(`https://jsonplaceholder.typicode.com/users`)
|
// submitForm.get('sendMessage', {
|
||||||
// .then(res => {
|
// params: {
|
||||||
// console.log(res);
|
// chat_id: 351813222,
|
||||||
// console.log(res.data);
|
// text:texttolog,
|
||||||
// })
|
// }
|
||||||
|
// }
|
||||||
let texttolog = JSON.stringify(payload)
|
// ).then(res => {
|
||||||
|
// console.log(res);
|
||||||
// submitForm.get('sendMessage', {
|
// console.log(res.data);
|
||||||
// params: {
|
// })
|
||||||
// chat_id: 351813222,
|
|
||||||
// text:texttolog,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ).then(res => {
|
|
||||||
// console.log(res);
|
|
||||||
// console.log(res.data);
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
submitForm.post('user?email='+texttolog
|
submitForm.post("user?email=" + texttolog).then((res) => {
|
||||||
).then(res => {
|
console.log(res);
|
||||||
console.log(res);
|
console.log(res.data);
|
||||||
console.log(res.data);
|
});
|
||||||
})
|
|
||||||
|
|
||||||
localStorage.setItem('isLoggedIn', 'yes');
|
localStorage.setItem("isLoggedIn", "yes");
|
||||||
props.history.push('/application')
|
props.history.push("/application");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="signup-form">
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
className="space-top"
|
||||||
|
style={{ width: "100%", paddingLeft: 32 }}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
className="title"
|
||||||
|
style={{
|
||||||
|
marginBottom: 0,
|
||||||
|
marginTop: 40,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <img src={"Signoz-white.svg"} alt="" style={{ height: 60 }} /> */}
|
||||||
|
Create your account
|
||||||
|
</h1>
|
||||||
|
<div className="page-caption">
|
||||||
|
Monitor your applications. Find what is causing issues.
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
<Row style={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<div
|
||||||
|
style={{ display: "flex", alignItems: "center", flexDirection: "column" }}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={"signoz.svg"}
|
||||||
|
style={{ maxHeight: "100%", maxWidth: 300, marginTop: 64 }}
|
||||||
|
alt=""
|
||||||
|
className="main-img"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
margin: "0 32px",
|
||||||
|
flexDirection: "column",
|
||||||
|
paddingTop: 32,
|
||||||
|
maxWidth: "32rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="input-set">
|
||||||
|
<label htmlFor="signupEmail">Email</label>
|
||||||
|
<Input
|
||||||
|
placeholder="mike@netflix.com"
|
||||||
|
type="email"
|
||||||
|
value={formState.email.value}
|
||||||
|
onChange={(e) => updateForm("email", e.target)}
|
||||||
|
required
|
||||||
|
// disabled={accountLoading}
|
||||||
|
id="signupEmail"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
<div
|
||||||
<div className="signup-form">
|
className={`input-set ${
|
||||||
<Space direction="vertical" className="space-top" style={{ width: '100%', paddingLeft: 32 }}>
|
state.submitted && !formState.password.valid ? "errored" : ""
|
||||||
<h1 className="title" style={{ marginBottom: 0, marginTop: 40, display: 'flex', alignItems: 'center' }}>
|
}`}
|
||||||
{/* <img src={"Signoz-white.svg"} alt="" style={{ height: 60 }} /> */}
|
>
|
||||||
Create your account
|
<label htmlFor="signupPassword">Password</label>
|
||||||
</h1>
|
<Input.Password
|
||||||
<div className="page-caption">Monitor your applications. Find what is causing issues.</div>
|
value={formState.password.value}
|
||||||
</Space>
|
onChange={(e) => updateForm("password", e.target)}
|
||||||
<Row style={{ display: 'flex', justifyContent: 'center' }}>
|
required
|
||||||
<div style={{ display: 'flex', alignItems: 'center', flexDirection: 'column' }}>
|
ref={passwordInput}
|
||||||
<img
|
// disabled={accountLoading}
|
||||||
src={"signoz.svg"}
|
id="signupPassword"
|
||||||
style={{ maxHeight: '100%', maxWidth: 300, marginTop: 64 }}
|
/>
|
||||||
alt=""
|
<Suspense fallback={<span />}>
|
||||||
className="main-img"
|
{/* <PasswordStrength password={formState.password.value} /> */}
|
||||||
/>
|
</Suspense>
|
||||||
</div>
|
{!formState.password && (
|
||||||
<div
|
<span className="caption">
|
||||||
style={{
|
Your password must have at least 8 characters.
|
||||||
display: 'flex',
|
</span>
|
||||||
justifyContent: 'flex-start',
|
)}
|
||||||
margin: '0 32px',
|
</div>
|
||||||
flexDirection: 'column',
|
|
||||||
paddingTop: 32,
|
|
||||||
maxWidth: '32rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="input-set">
|
|
||||||
<label htmlFor="signupEmail">Email</label>
|
|
||||||
<Input
|
|
||||||
placeholder="mike@netflix.com"
|
|
||||||
type="email"
|
|
||||||
value={formState.email.value}
|
|
||||||
onChange={(e) => updateForm('email', e.target)}
|
|
||||||
required
|
|
||||||
// disabled={accountLoading}
|
|
||||||
id="signupEmail"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`input-set ${state.submitted && !formState.password.valid ? 'errored' : ''}`}>
|
<div className="input-set">
|
||||||
<label htmlFor="signupPassword">Password</label>
|
<label htmlFor="signupFirstName">First Name</label>
|
||||||
<Input.Password
|
<Input
|
||||||
value={formState.password.value}
|
placeholder="Mike"
|
||||||
onChange={(e) => updateForm('password', e.target)}
|
autoFocus
|
||||||
required
|
value={formState.firstName.value}
|
||||||
ref={passwordInput}
|
onChange={(e) => updateForm("firstName", e.target)}
|
||||||
// disabled={accountLoading}
|
required
|
||||||
id="signupPassword"
|
// disabled={accountLoading}
|
||||||
/>
|
id="signupFirstName"
|
||||||
<Suspense fallback={<span />}>
|
/>
|
||||||
{/* <PasswordStrength password={formState.password.value} /> */}
|
</div>
|
||||||
</Suspense>
|
|
||||||
{!formState.password && (
|
|
||||||
<span className="caption">Your password must have at least 8 characters.</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="input-set">
|
<div className="input-set">
|
||||||
<label htmlFor="signupFirstName">First Name</label>
|
<label htmlFor="signupCompanyName">Company or Project</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Mike"
|
placeholder="Netflix"
|
||||||
autoFocus
|
value={formState.companyName.value}
|
||||||
value={formState.firstName.value}
|
onChange={(e) => updateForm("companyName", e.target)}
|
||||||
onChange={(e) => updateForm('firstName', e.target)}
|
// disabled={accountLoading}
|
||||||
required
|
id="signupCompanyName"
|
||||||
// disabled={accountLoading}
|
/>
|
||||||
id="signupFirstName"
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="input-set">
|
<div>
|
||||||
<label htmlFor="signupCompanyName">Company or Project</label>
|
<Checkbox
|
||||||
<Input
|
checked={formState.emailOptIn.value}
|
||||||
placeholder="Netflix"
|
onChange={(e) => updateForm("emailOptIn", e.target, "checked")}
|
||||||
value={formState.companyName.value}
|
// disabled={accountLoading}
|
||||||
onChange={(e) => updateForm('companyName', e.target)}
|
>
|
||||||
// disabled={accountLoading}
|
Send me occasional emails about security and product updates. You may
|
||||||
id="signupCompanyName"
|
unsubscribe at any time.
|
||||||
/>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-center space-top">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
data-attr="signup"
|
||||||
|
disabled={state.submitted && !formState.password}
|
||||||
|
// loading={accountLoading}
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
{/* <div style={{ color: '#666666', marginBottom: 60, textAlign: 'center' }} className="space-top">
|
||||||
<Checkbox
|
|
||||||
checked={formState.emailOptIn.value}
|
|
||||||
onChange={(e) => updateForm('emailOptIn', e.target, 'checked')}
|
|
||||||
// disabled={accountLoading}
|
|
||||||
>
|
|
||||||
Send me occasional emails about security and product updates. You may unsubscribe at any
|
|
||||||
time.
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
<div className="text-center space-top">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
data-attr="signup"
|
|
||||||
disabled={state.submitted && !formState.password}
|
|
||||||
// loading={accountLoading}
|
|
||||||
>
|
|
||||||
Get Started
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <div style={{ color: '#666666', marginBottom: 60, textAlign: 'center' }} className="space-top">
|
|
||||||
By clicking the button above you agree to our{' '}
|
By clicking the button above you agree to our{' '}
|
||||||
<a href="https://signoz.io" target="_blank">
|
<a href="https://signoz.io" target="_blank">
|
||||||
Terms of Service
|
Terms of Service
|
||||||
@ -224,12 +243,11 @@ const Signup = (props:SignUpProps) => {
|
|||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
</div> */}
|
</div> */}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
);
|
export default withRouter(Signup);
|
||||||
}
|
|
||||||
|
|
||||||
export default withRouter(Signup);
|
|
||||||
|
3
frontend/src/components/constants/localStorage.ts
Normal file
3
frontend/src/components/constants/localStorage.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export enum LocalStorage {
|
||||||
|
metricsTimeDuration = "metricsTimeDuration",
|
||||||
|
}
|
3
frontend/src/components/constants/query.ts
Normal file
3
frontend/src/components/constants/query.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export enum MetricQueryParama {
|
||||||
|
time = "time",
|
||||||
|
}
|
@ -1,240 +1,228 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
import { Line as ChartJSLine } from "react-chartjs-2";
|
||||||
import { ChartOptions } from 'chart.js';
|
import { ChartOptions } from "chart.js";
|
||||||
import { withRouter } from "react-router";
|
import { withRouter } from "react-router";
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { metricItem } from '../../actions/metrics'
|
import { metricItem } from "../../actions/metrics";
|
||||||
|
|
||||||
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>`
|
const ChartPopUpUnique = styled.div<{
|
||||||
background-color:white;
|
ycoordinate: number;
|
||||||
border:1px solid rgba(219,112,147,0.5);
|
xcoordinate: number;
|
||||||
zIndex:10;
|
}>`
|
||||||
position:absolute;
|
background-color: white;
|
||||||
top:${props => props.ycoordinate}px;
|
border: 1px solid rgba(219, 112, 147, 0.5);
|
||||||
left:${props => props.xcoordinate}px;
|
zindex: 10;
|
||||||
font-size:12px;
|
position: absolute;
|
||||||
border-radius:2px;
|
top: ${(props) => props.ycoordinate}px;
|
||||||
|
left: ${(props) => props.xcoordinate}px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PopUpElements = styled.p`
|
const PopUpElements = styled.p`
|
||||||
color:black;
|
color: black;
|
||||||
margin-bottom:0px;
|
margin-bottom: 0px;
|
||||||
padding-left:4px;
|
padding-left: 4px;
|
||||||
padding-right:4px;
|
padding-right: 4px;
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor:pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
// PNOTE - Check if this should be the case
|
// PNOTE - Check if this should be the case
|
||||||
const theme = 'dark';
|
const theme = "dark";
|
||||||
|
|
||||||
interface ErrorRateChartProps extends RouteComponentProps<any> {
|
interface ErrorRateChartProps extends RouteComponentProps<any> {
|
||||||
data : metricItem[],
|
data: metricItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ErrorRateChart {
|
interface ErrorRateChart {
|
||||||
chartRef: any;
|
chartRef: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ErrorRateChart extends React.Component<ErrorRateChartProps> {
|
||||||
|
constructor(props: ErrorRateChartProps) {
|
||||||
|
super(props);
|
||||||
|
this.chartRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
class ErrorRateChart extends React.Component<ErrorRateChartProps>{
|
state = {
|
||||||
|
// data: props.data,
|
||||||
|
xcoordinate: 0,
|
||||||
|
ycoordinate: 0,
|
||||||
|
showpopUp: false,
|
||||||
|
// graphInfo:{}
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: ErrorRateChartProps) {
|
onClickhandler = async (e: any, event: any) => {
|
||||||
super(props);
|
var firstPoint;
|
||||||
this.chartRef = React.createRef();
|
if (this.chartRef) {
|
||||||
}
|
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstPoint) {
|
||||||
|
// PNOTE - TODO - Is await needed in this expression?
|
||||||
|
await this.setState({
|
||||||
|
xcoordinate: e.offsetX + 20,
|
||||||
|
ycoordinate: e.offsetY,
|
||||||
|
showpopUp: true,
|
||||||
|
// graphInfo:{...event}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
gotoTracesHandler = () => {
|
||||||
// data: props.data,
|
this.props.history.push("/traces");
|
||||||
xcoordinate:0,
|
};
|
||||||
ycoordinate:0,
|
|
||||||
showpopUp:false,
|
|
||||||
// graphInfo:{}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onClickhandler = async(e:any,event:any) => {
|
gotoAlertsHandler = () => {
|
||||||
|
this.props.history.push("/service-map");
|
||||||
var firstPoint;
|
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||||
if(this.chartRef){
|
};
|
||||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstPoint)
|
|
||||||
{// PNOTE - TODO - Is await needed in this expression?
|
|
||||||
await this.setState({
|
|
||||||
xcoordinate:e.offsetX+20,
|
|
||||||
ycoordinate:e.offsetY,
|
|
||||||
showpopUp:true,
|
|
||||||
// graphInfo:{...event}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gotoTracesHandler=()=>{
|
options_charts: ChartOptions = {
|
||||||
this.props.history.push('/traces')
|
onClick: this.onClickhandler,
|
||||||
}
|
|
||||||
|
|
||||||
gotoAlertsHandler=()=>{
|
maintainAspectRatio: true,
|
||||||
this.props.history.push('/service-map')
|
responsive: true,
|
||||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
|
||||||
}
|
|
||||||
|
|
||||||
options_charts: ChartOptions = {
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "Error per sec",
|
||||||
|
fontSize: 20,
|
||||||
|
position: "top",
|
||||||
|
padding: 2,
|
||||||
|
fontFamily: "Arial",
|
||||||
|
fontStyle: "regular",
|
||||||
|
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||||
|
},
|
||||||
|
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: "bottom",
|
||||||
|
align: "center",
|
||||||
|
|
||||||
onClick: this.onClickhandler,
|
labels: {
|
||||||
|
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||||
|
fontSize: 10,
|
||||||
|
boxWidth: 10,
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
maintainAspectRatio: true,
|
tooltips: {
|
||||||
responsive: true,
|
mode: "label",
|
||||||
|
bodyFontSize: 12,
|
||||||
|
titleFontSize: 12,
|
||||||
|
|
||||||
title: {
|
callbacks: {
|
||||||
display: true,
|
label: function (tooltipItem, data) {
|
||||||
text: 'Error per sec',
|
if (typeof tooltipItem.yLabel === "number") {
|
||||||
fontSize: 20,
|
return (
|
||||||
position:'top',
|
data.datasets![tooltipItem.datasetIndex!].label +
|
||||||
padding: 2,
|
" : " +
|
||||||
fontFamily: 'Arial',
|
tooltipItem.yLabel.toFixed(2)
|
||||||
fontStyle: 'regular',
|
);
|
||||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
scales: {
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
stacked: false,
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: false,
|
||||||
|
fontSize: 10,
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 6,
|
||||||
|
},
|
||||||
|
|
||||||
},
|
// scaleLabel: {
|
||||||
|
// display: true,
|
||||||
|
// labelString: 'latency in ms',
|
||||||
|
// fontSize: 6,
|
||||||
|
// padding: 4,
|
||||||
|
// },
|
||||||
|
gridLines: {
|
||||||
|
// You can change the color, the dash effect, the main axe color, etc.
|
||||||
|
borderDash: [1, 4],
|
||||||
|
color: "#D3D3D3",
|
||||||
|
lineWidth: 0.25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
type: "time",
|
||||||
|
// time: {
|
||||||
|
// unit: 'second'
|
||||||
|
// },
|
||||||
|
distribution: "linear",
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: false,
|
||||||
|
fontSize: 10,
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 10,
|
||||||
|
},
|
||||||
|
// gridLines: false, --> not a valid option
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
legend: {
|
GraphTracePopUp = () => {
|
||||||
display: true,
|
if (this.state.showpopUp) {
|
||||||
position: 'bottom',
|
return (
|
||||||
align: 'center',
|
<ChartPopUpUnique
|
||||||
|
xcoordinate={this.state.xcoordinate}
|
||||||
labels: {
|
ycoordinate={this.state.ycoordinate}
|
||||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
>
|
||||||
fontSize: 10,
|
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
||||||
boxWidth : 10,
|
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||||
usePointStyle : true,
|
</ChartPopUpUnique>
|
||||||
|
);
|
||||||
|
} else return null;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
render() {
|
||||||
},
|
const ndata = this.props.data;
|
||||||
|
|
||||||
tooltips: {
|
const data_chartJS = (canvas: any) => {
|
||||||
mode: 'label',
|
const ctx = canvas.getContext("2d");
|
||||||
bodyFontSize: 12,
|
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
||||||
titleFontSize: 12,
|
gradient.addColorStop(0, "rgba(250,174,50,1)");
|
||||||
|
gradient.addColorStop(1, "rgba(250,174,50,1)");
|
||||||
callbacks: {
|
return {
|
||||||
label: function(tooltipItem, data) {
|
labels: ndata.map((s) => new Date(s.timestamp / 1000000)), // converting from nano second to mili second
|
||||||
|
datasets: [
|
||||||
if (typeof(tooltipItem.yLabel) === 'number')
|
{
|
||||||
{
|
label: "Errors per sec",
|
||||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2);
|
data: ndata.map((s) => s.errorRate),
|
||||||
}
|
pointRadius: 0.5,
|
||||||
else
|
borderColor: "rgba(227, 74, 51,1)", // Can also add transparency in border color
|
||||||
{
|
borderWidth: 2,
|
||||||
return '';
|
},
|
||||||
}
|
],
|
||||||
|
};
|
||||||
},
|
};
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
scales: {
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
stacked: false,
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false,
|
|
||||||
fontSize: 10,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 6,
|
|
||||||
},
|
|
||||||
|
|
||||||
// scaleLabel: {
|
|
||||||
// display: true,
|
|
||||||
// labelString: 'latency in ms',
|
|
||||||
// fontSize: 6,
|
|
||||||
// padding: 4,
|
|
||||||
// },
|
|
||||||
gridLines: {
|
|
||||||
// You can change the color, the dash effect, the main axe color, etc.
|
|
||||||
borderDash: [1, 4],
|
|
||||||
color: "#D3D3D3",
|
|
||||||
lineWidth: 0.25,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
xAxes: [{
|
|
||||||
type: 'time',
|
|
||||||
// time: {
|
|
||||||
// unit: 'second'
|
|
||||||
// },
|
|
||||||
distribution:'linear',
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false,
|
|
||||||
fontSize: 10,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 10,
|
|
||||||
},
|
|
||||||
// gridLines: false, --> not a valid option
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphTracePopUp = () => {
|
|
||||||
|
|
||||||
if (this.state.showpopUp){
|
|
||||||
return(
|
|
||||||
|
|
||||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
|
||||||
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
|
||||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
|
||||||
</ChartPopUpUnique>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
render(){
|
|
||||||
|
|
||||||
const ndata = this.props.data;
|
|
||||||
|
|
||||||
const data_chartJS = (canvas:any) => {
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
|
||||||
gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
|
||||||
gradient.addColorStop(1, 'rgba(250,174,50,1)');
|
|
||||||
return{labels: ndata.map(s => new Date(s.timestamp/1000000)), // converting from nano second to mili second
|
|
||||||
datasets: [{
|
|
||||||
label: 'Errors per sec',
|
|
||||||
data: ndata.map(s => s.errorRate),
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(227, 74, 51,1)', // Can also add transparency in border color
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
]}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
{this.GraphTracePopUp()}
|
|
||||||
<ChartJSLine ref={this.chartRef} data={data_chartJS} options={this.options_charts} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.GraphTracePopUp()}
|
||||||
|
<ChartJSLine
|
||||||
|
ref={this.chartRef}
|
||||||
|
data={data_chartJS}
|
||||||
|
options={this.options_charts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(ErrorRateChart);
|
export default withRouter(ErrorRateChart);
|
||||||
|
@ -1,80 +1,78 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Bar, Line as ChartJSLine } from 'react-chartjs-2';
|
import { Bar, Line as ChartJSLine } from "react-chartjs-2";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { customMetricsItem } from '../../actions/metrics'
|
import { customMetricsItem } from "../../actions/metrics";
|
||||||
|
|
||||||
const GenVisualizationWrapper = styled.div`
|
const GenVisualizationWrapper = styled.div`
|
||||||
height:160px;
|
height: 160px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface GenericVisualizationsProps {
|
interface GenericVisualizationsProps {
|
||||||
chartType: string;
|
chartType: string;
|
||||||
data: customMetricsItem[];
|
data: customMetricsItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const GenericVisualizations = (props: GenericVisualizationsProps) => {
|
const GenericVisualizations = (props: GenericVisualizationsProps) => {
|
||||||
|
const data = {
|
||||||
|
labels: props.data.map((s) => new Date(s.timestamp / 1000000)),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: props.data.map((s) => s.value),
|
||||||
|
borderColor: "rgba(250,174,50,1)", // for line chart
|
||||||
|
backgroundColor: props.chartType === "bar" ? "rgba(250,174,50,1)" : "", // for bar chart, don't assign backgroundcolor if its not a bar chart, may be relevant for area graph though
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const data = {
|
const options = {
|
||||||
labels: props.data.map(s => new Date(s.timestamp/1000000)),
|
responsive: true,
|
||||||
datasets: [{
|
maintainAspectRatio: false,
|
||||||
data: props.data.map(s => s.value),
|
legend: {
|
||||||
borderColor: 'rgba(250,174,50,1)',// for line chart
|
display: false,
|
||||||
backgroundColor: props.chartType==='bar'?'rgba(250,174,50,1)':'', // for bar chart, don't assign backgroundcolor if its not a bar chart, may be relevant for area graph though
|
},
|
||||||
},
|
scales: {
|
||||||
]
|
yAxes: [
|
||||||
};
|
{
|
||||||
|
gridLines: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
type: "time",
|
||||||
|
// distribution: 'linear',
|
||||||
|
//'linear': data are spread according to their time (distances can vary)
|
||||||
|
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: false,
|
||||||
|
fontSize: 10,
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 10,
|
||||||
|
},
|
||||||
|
// gridLines: false, --> not a valid option
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const options= {
|
if (props.chartType === "line") {
|
||||||
responsive: true,
|
return (
|
||||||
maintainAspectRatio: false,
|
<GenVisualizationWrapper>
|
||||||
legend: {
|
<ChartJSLine data={data} options={options} />
|
||||||
display: false,
|
</GenVisualizationWrapper>
|
||||||
},
|
);
|
||||||
scales: {
|
} else if (props.chartType === "bar") {
|
||||||
yAxes: [{
|
return (
|
||||||
gridLines: {
|
<GenVisualizationWrapper>
|
||||||
drawBorder: false,
|
<Bar data={data} options={options} />
|
||||||
},
|
</GenVisualizationWrapper>
|
||||||
ticks: {
|
);
|
||||||
display: false
|
} else return null;
|
||||||
}
|
};
|
||||||
}],
|
|
||||||
xAxes: [{
|
|
||||||
type: 'time',
|
|
||||||
// distribution: 'linear',
|
|
||||||
//'linear': data are spread according to their time (distances can vary)
|
|
||||||
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false,
|
|
||||||
fontSize: 10,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 10,
|
|
||||||
},
|
|
||||||
// gridLines: false, --> not a valid option
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if(props.chartType === 'line')
|
export default GenericVisualizations;
|
||||||
{
|
|
||||||
return (
|
|
||||||
<GenVisualizationWrapper>
|
|
||||||
<ChartJSLine data={data} options={options} />
|
|
||||||
</GenVisualizationWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
} else if (props.chartType === 'bar')
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<GenVisualizationWrapper>
|
|
||||||
<Bar data={data} options={options} />
|
|
||||||
</GenVisualizationWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GenericVisualizations;
|
|
||||||
|
@ -1,257 +1,248 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
import { Line as ChartJSLine } from "react-chartjs-2";
|
||||||
import { ChartOptions } from 'chart.js';
|
import { ChartOptions } from "chart.js";
|
||||||
import { withRouter } from "react-router";
|
import { withRouter } from "react-router";
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { metricItem } from '../../actions/metrics'
|
import { metricItem } from "../../actions/metrics";
|
||||||
|
|
||||||
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>`
|
const ChartPopUpUnique = styled.div<{
|
||||||
background-color:white;
|
ycoordinate: number;
|
||||||
border:1px solid rgba(219,112,147,0.5);
|
xcoordinate: number;
|
||||||
zIndex:10;
|
}>`
|
||||||
position:absolute;
|
background-color: white;
|
||||||
top:${props => props.ycoordinate}px;
|
border: 1px solid rgba(219, 112, 147, 0.5);
|
||||||
left:${props => props.xcoordinate}px;
|
zindex: 10;
|
||||||
font-size:12px;
|
position: absolute;
|
||||||
border-radius:2px;
|
top: ${(props) => props.ycoordinate}px;
|
||||||
|
left: ${(props) => props.xcoordinate}px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PopUpElements = styled.p`
|
const PopUpElements = styled.p`
|
||||||
color:black;
|
color: black;
|
||||||
margin-bottom:0px;
|
margin-bottom: 0px;
|
||||||
padding-left:4px;
|
padding-left: 4px;
|
||||||
padding-right:4px;
|
padding-right: 4px;
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor:pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const theme = "dark";
|
||||||
const theme = 'dark';
|
|
||||||
|
|
||||||
|
|
||||||
interface LatencyLineChartProps extends RouteComponentProps<any> {
|
interface LatencyLineChartProps extends RouteComponentProps<any> {
|
||||||
data : metricItem[],
|
data: metricItem[];
|
||||||
popupClickHandler: Function,
|
popupClickHandler: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LatencyLineChart {
|
interface LatencyLineChart {
|
||||||
chartRef: any;
|
chartRef: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LatencyLineChart extends React.Component<LatencyLineChartProps> {
|
||||||
|
constructor(props: LatencyLineChartProps) {
|
||||||
|
super(props);
|
||||||
|
this.chartRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
class LatencyLineChart extends React.Component<LatencyLineChartProps>{
|
state = {
|
||||||
|
xcoordinate: 0,
|
||||||
|
ycoordinate: 0,
|
||||||
|
showpopUp: false,
|
||||||
|
firstpoint_ts: 0,
|
||||||
|
// graphInfo:{}
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: LatencyLineChartProps) {
|
onClickhandler = async (e: any, event: any) => {
|
||||||
super(props);
|
var firstPoint;
|
||||||
this.chartRef = React.createRef();
|
if (this.chartRef) {
|
||||||
}
|
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstPoint) {
|
||||||
|
this.setState({
|
||||||
|
xcoordinate: e.offsetX + 20,
|
||||||
|
ycoordinate: e.offsetY,
|
||||||
|
showpopUp: true,
|
||||||
|
firstpoint_ts: this.props.data[firstPoint._index].timestamp,
|
||||||
|
// graphInfo:{...event}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if clicked outside of the graph line, then firstpoint is undefined -> close popup.
|
||||||
|
// Only works for clicking in the same chart - as click handler only detects clicks in that chart
|
||||||
|
this.setState({
|
||||||
|
showpopUp: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
gotoTracesHandler = (xc: any) => {
|
||||||
xcoordinate:0,
|
this.props.history.push("/traces");
|
||||||
ycoordinate:0,
|
};
|
||||||
showpopUp:false,
|
|
||||||
firstpoint_ts:0,
|
|
||||||
// graphInfo:{}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onClickhandler = async(e:any,event:any) => {
|
gotoAlertsHandler = () => {
|
||||||
|
this.props.history.push("/service-map");
|
||||||
|
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||||
|
};
|
||||||
|
|
||||||
var firstPoint;
|
options_charts: ChartOptions = {
|
||||||
if(this.chartRef){
|
onClick: this.onClickhandler,
|
||||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstPoint)
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
xcoordinate:e.offsetX+20,
|
|
||||||
ycoordinate:e.offsetY,
|
|
||||||
showpopUp:true,
|
|
||||||
firstpoint_ts:this.props.data[firstPoint._index].timestamp,
|
|
||||||
// graphInfo:{...event}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// if clicked outside of the graph line, then firstpoint is undefined -> close popup.
|
|
||||||
// Only works for clicking in the same chart - as click handler only detects clicks in that chart
|
|
||||||
this.setState({
|
|
||||||
|
|
||||||
showpopUp:false,
|
maintainAspectRatio: true,
|
||||||
|
responsive: true,
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gotoTracesHandler=(xc:any)=>{
|
title: {
|
||||||
this.props.history.push('/traces')
|
display: true,
|
||||||
}
|
text: "Application Latency in ms",
|
||||||
|
fontSize: 20,
|
||||||
|
position: "top",
|
||||||
|
padding: 8,
|
||||||
|
fontFamily: "Arial",
|
||||||
|
fontStyle: "regular",
|
||||||
|
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||||
|
},
|
||||||
|
|
||||||
gotoAlertsHandler=()=>{
|
legend: {
|
||||||
this.props.history.push('/service-map')
|
display: true,
|
||||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
position: "bottom",
|
||||||
}
|
align: "center",
|
||||||
|
|
||||||
options_charts: ChartOptions = {
|
labels: {
|
||||||
|
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||||
|
fontSize: 10,
|
||||||
|
boxWidth: 10,
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
onClick: this.onClickhandler,
|
tooltips: {
|
||||||
|
mode: "label",
|
||||||
|
bodyFontSize: 12,
|
||||||
|
titleFontSize: 12,
|
||||||
|
|
||||||
maintainAspectRatio: true,
|
callbacks: {
|
||||||
responsive: true,
|
label: function (tooltipItem, data) {
|
||||||
|
if (typeof tooltipItem.yLabel === "number") {
|
||||||
|
return (
|
||||||
|
data.datasets![tooltipItem.datasetIndex!].label +
|
||||||
|
" : " +
|
||||||
|
tooltipItem.yLabel.toFixed(2)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
title: {
|
scales: {
|
||||||
display: true,
|
yAxes: [
|
||||||
text: 'Application Latency in ms',
|
{
|
||||||
fontSize: 20,
|
stacked: false,
|
||||||
position:'top',
|
ticks: {
|
||||||
padding: 8,
|
beginAtZero: false,
|
||||||
fontFamily: 'Arial',
|
fontSize: 10,
|
||||||
fontStyle: 'regular',
|
autoSkip: true,
|
||||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
maxTicksLimit: 6,
|
||||||
},
|
},
|
||||||
|
|
||||||
legend: {
|
gridLines: {
|
||||||
display: true,
|
// You can change the color, the dash effect, the main axe color, etc.
|
||||||
position: 'bottom',
|
borderDash: [1, 4],
|
||||||
align: 'center',
|
color: "#D3D3D3",
|
||||||
|
lineWidth: 0.25,
|
||||||
labels: {
|
},
|
||||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
},
|
||||||
fontSize: 10,
|
],
|
||||||
boxWidth : 10,
|
xAxes: [
|
||||||
usePointStyle : true,
|
{
|
||||||
|
type: "time",
|
||||||
|
// time: {
|
||||||
|
// unit: 'second'
|
||||||
|
// },
|
||||||
|
distribution: "linear",
|
||||||
|
//'linear': data are spread according to their time (distances can vary)
|
||||||
|
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: false,
|
||||||
|
fontSize: 10,
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 10,
|
||||||
|
},
|
||||||
|
// gridLines: false, --> not a valid option
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
}
|
GraphTracePopUp = () => {
|
||||||
},
|
if (this.state.showpopUp) {
|
||||||
|
return (
|
||||||
|
<ChartPopUpUnique
|
||||||
|
xcoordinate={this.state.xcoordinate}
|
||||||
|
ycoordinate={this.state.ycoordinate}
|
||||||
|
>
|
||||||
|
<PopUpElements
|
||||||
|
onClick={() => this.props.popupClickHandler(this.state.firstpoint_ts)}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</PopUpElements>
|
||||||
|
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||||
|
</ChartPopUpUnique>
|
||||||
|
);
|
||||||
|
} else return null;
|
||||||
|
};
|
||||||
|
|
||||||
tooltips: {
|
render() {
|
||||||
mode: 'label',
|
const ndata = this.props.data;
|
||||||
bodyFontSize: 12,
|
|
||||||
titleFontSize: 12,
|
|
||||||
|
|
||||||
callbacks: {
|
const data_chartJS = (canvas: any) => {
|
||||||
label: function(tooltipItem, data) {
|
const ctx = canvas.getContext("2d");
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
||||||
if (typeof(tooltipItem.yLabel) === 'number')
|
gradient.addColorStop(0, "rgba(250,174,50,1)");
|
||||||
{
|
gradient.addColorStop(1, "rgba(250,174,50,1)");
|
||||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2);
|
return {
|
||||||
}
|
labels: ndata.map((s) => new Date(s.timestamp / 1000000)),
|
||||||
else
|
datasets: [
|
||||||
{
|
{
|
||||||
return '';
|
label: "p99 Latency",
|
||||||
}
|
data: ndata.map((s) => s.p99 / 1000000), //converting latency from nano sec to ms
|
||||||
},
|
pointRadius: 0.5,
|
||||||
},
|
borderColor: "rgba(250,174,50,1)", // Can also add transparency in border color
|
||||||
},
|
borderWidth: 2,
|
||||||
|
},
|
||||||
scales: {
|
{
|
||||||
yAxes: [
|
label: "p90 Latency",
|
||||||
{
|
data: ndata.map((s) => s.p90 / 1000000), //converting latency from nano sec to ms
|
||||||
stacked: false,
|
pointRadius: 0.5,
|
||||||
ticks: {
|
borderColor: "rgba(227, 74, 51, 1.0)",
|
||||||
beginAtZero: false,
|
borderWidth: 2,
|
||||||
fontSize: 10,
|
},
|
||||||
autoSkip: true,
|
{
|
||||||
maxTicksLimit: 6,
|
label: "p50 Latency",
|
||||||
},
|
data: ndata.map((s) => s.p50 / 1000000), //converting latency from nano sec to ms
|
||||||
|
pointRadius: 0.5,
|
||||||
gridLines: {
|
borderColor: "rgba(57, 255, 20, 1.0)",
|
||||||
// You can change the color, the dash effect, the main axe color, etc.
|
borderWidth: 2,
|
||||||
borderDash: [1, 4],
|
},
|
||||||
color: "#D3D3D3",
|
],
|
||||||
lineWidth: 0.25,
|
};
|
||||||
}
|
};
|
||||||
},
|
|
||||||
],
|
|
||||||
xAxes: [{
|
|
||||||
type: 'time',
|
|
||||||
// time: {
|
|
||||||
// unit: 'second'
|
|
||||||
// },
|
|
||||||
distribution: 'linear',
|
|
||||||
//'linear': data are spread according to their time (distances can vary)
|
|
||||||
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false,
|
|
||||||
fontSize: 10,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 10,
|
|
||||||
},
|
|
||||||
// gridLines: false, --> not a valid option
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphTracePopUp = () => {
|
|
||||||
|
|
||||||
if (this.state.showpopUp){
|
|
||||||
return(
|
|
||||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
|
||||||
<PopUpElements onClick={() => this.props.popupClickHandler(this.state.firstpoint_ts)}>View Traces</PopUpElements>
|
|
||||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
|
||||||
</ChartPopUpUnique>
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
render(){
|
|
||||||
|
|
||||||
const ndata = this.props.data;
|
|
||||||
|
|
||||||
const data_chartJS = (canvas:any) => {
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
|
||||||
gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
|
||||||
gradient.addColorStop(1, 'rgba(250,174,50,1)');
|
|
||||||
return{ labels: ndata.map(s => new Date(s.timestamp/1000000)),
|
|
||||||
datasets: [{
|
|
||||||
label: 'p99 Latency',
|
|
||||||
data: ndata.map(s => s.p99/1000000), //converting latency from nano sec to ms
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(250,174,50,1)', // Can also add transparency in border color
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'p90 Latency',
|
|
||||||
data: ndata.map(s => s.p90/1000000), //converting latency from nano sec to ms
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(227, 74, 51, 1.0)',
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'p50 Latency',
|
|
||||||
data: ndata.map(s => s.p50/1000000), //converting latency from nano sec to ms
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(57, 255, 20, 1.0)',
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
{this.GraphTracePopUp()}
|
|
||||||
<ChartJSLine ref={this.chartRef} data={data_chartJS} options={this.options_charts} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.GraphTracePopUp()}
|
||||||
|
<ChartJSLine
|
||||||
|
ref={this.chartRef}
|
||||||
|
data={data_chartJS}
|
||||||
|
options={this.options_charts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(LatencyLineChart);
|
export default withRouter(LatencyLineChart);
|
||||||
|
@ -1,223 +1,220 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
import { Line as ChartJSLine } from "react-chartjs-2";
|
||||||
import { ChartOptions } from 'chart.js';
|
import { ChartOptions } from "chart.js";
|
||||||
import { withRouter } from "react-router";
|
import { withRouter } from "react-router";
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { metricItem } from '../../actions/metrics'
|
import { metricItem } from "../../actions/metrics";
|
||||||
|
|
||||||
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>`
|
const ChartPopUpUnique = styled.div<{
|
||||||
background-color:white;
|
ycoordinate: number;
|
||||||
border:1px solid rgba(219,112,147,0.5);
|
xcoordinate: number;
|
||||||
zIndex:10;
|
}>`
|
||||||
position:absolute;
|
background-color: white;
|
||||||
top:${props => props.ycoordinate}px;
|
border: 1px solid rgba(219, 112, 147, 0.5);
|
||||||
left:${props => props.xcoordinate}px;
|
zindex: 10;
|
||||||
font-size:12px;
|
position: absolute;
|
||||||
border-radius:2px;
|
top: ${(props) => props.ycoordinate}px;
|
||||||
|
left: ${(props) => props.xcoordinate}px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PopUpElements = styled.p`
|
const PopUpElements = styled.p`
|
||||||
color:black;
|
color: black;
|
||||||
margin-bottom:0px;
|
margin-bottom: 0px;
|
||||||
padding-left:4px;
|
padding-left: 4px;
|
||||||
padding-right:4px;
|
padding-right: 4px;
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor:pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const theme = "dark";
|
||||||
const theme = 'dark';
|
|
||||||
|
|
||||||
interface RequestRateChartProps extends RouteComponentProps<any> {
|
interface RequestRateChartProps extends RouteComponentProps<any> {
|
||||||
data : metricItem[],
|
data: metricItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RequestRateChart {
|
interface RequestRateChart {
|
||||||
chartRef: any;
|
chartRef: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RequestRateChart extends React.Component<RequestRateChartProps> {
|
||||||
|
constructor(props: RequestRateChartProps) {
|
||||||
|
super(props);
|
||||||
|
this.chartRef = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
class RequestRateChart extends React.Component<RequestRateChartProps>{
|
state = {
|
||||||
|
xcoordinate: 0,
|
||||||
|
ycoordinate: 0,
|
||||||
|
showpopUp: false,
|
||||||
|
// graphInfo:{}
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: RequestRateChartProps) {
|
onClickhandler = async (e: any, event: any) => {
|
||||||
super(props);
|
var firstPoint;
|
||||||
this.chartRef = React.createRef();
|
if (this.chartRef) {
|
||||||
}
|
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||||
|
}
|
||||||
|
|
||||||
state = {
|
if (firstPoint) {
|
||||||
xcoordinate:0,
|
// PNOTE - TODO - Is await needed in this expression?
|
||||||
ycoordinate:0,
|
await this.setState({
|
||||||
showpopUp:false,
|
xcoordinate: e.offsetX + 20,
|
||||||
// graphInfo:{}
|
ycoordinate: e.offsetY,
|
||||||
}
|
showpopUp: true,
|
||||||
|
// graphInfo:{...event}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onClickhandler = async(e:any,event:any) => {
|
gotoTracesHandler = () => {
|
||||||
|
this.props.history.push("/traces");
|
||||||
|
};
|
||||||
|
|
||||||
var firstPoint;
|
gotoAlertsHandler = () => {
|
||||||
if(this.chartRef){
|
this.props.history.push("/service-map");
|
||||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||||
}
|
};
|
||||||
|
|
||||||
if (firstPoint)
|
|
||||||
{// PNOTE - TODO - Is await needed in this expression?
|
|
||||||
await this.setState({
|
|
||||||
xcoordinate:e.offsetX+20,
|
|
||||||
ycoordinate:e.offsetY,
|
|
||||||
showpopUp:true,
|
|
||||||
// graphInfo:{...event}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
options_charts: ChartOptions = {
|
||||||
|
onClick: this.onClickhandler,
|
||||||
|
|
||||||
gotoTracesHandler=()=>{
|
maintainAspectRatio: true,
|
||||||
this.props.history.push('/traces')
|
responsive: true,
|
||||||
}
|
|
||||||
|
|
||||||
gotoAlertsHandler=()=>{
|
title: {
|
||||||
this.props.history.push('/service-map')
|
display: true,
|
||||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
text: "Request per sec",
|
||||||
}
|
fontSize: 20,
|
||||||
|
position: "top",
|
||||||
|
padding: 2,
|
||||||
|
fontFamily: "Arial",
|
||||||
|
fontStyle: "regular",
|
||||||
|
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||||
|
},
|
||||||
|
|
||||||
options_charts: ChartOptions = {
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: "bottom",
|
||||||
|
align: "center",
|
||||||
|
|
||||||
onClick: this.onClickhandler,
|
labels: {
|
||||||
|
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||||
|
fontSize: 10,
|
||||||
|
boxWidth: 10,
|
||||||
|
usePointStyle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
maintainAspectRatio: true,
|
tooltips: {
|
||||||
responsive: true,
|
mode: "label",
|
||||||
|
bodyFontSize: 12,
|
||||||
|
titleFontSize: 12,
|
||||||
|
|
||||||
title: {
|
callbacks: {
|
||||||
display: true,
|
label: function (tooltipItem, data) {
|
||||||
text: 'Request per sec',
|
if (typeof tooltipItem.yLabel === "number") {
|
||||||
fontSize: 20,
|
return (
|
||||||
position:'top',
|
data.datasets![tooltipItem.datasetIndex!].label +
|
||||||
padding: 2,
|
" : " +
|
||||||
fontFamily: 'Arial',
|
tooltipItem.yLabel.toFixed(2)
|
||||||
fontStyle: 'regular',
|
);
|
||||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
} else {
|
||||||
},
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
legend: {
|
scales: {
|
||||||
display: true,
|
yAxes: [
|
||||||
position: 'bottom',
|
{
|
||||||
align: 'center',
|
stacked: false,
|
||||||
|
ticks: {
|
||||||
labels: {
|
beginAtZero: false,
|
||||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
fontSize: 10,
|
||||||
fontSize: 10,
|
autoSkip: true,
|
||||||
boxWidth : 10,
|
maxTicksLimit: 6,
|
||||||
usePointStyle : true,
|
},
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tooltips: {
|
gridLines: {
|
||||||
mode: 'label',
|
// You can change the color, the dash effect, the main axe color, etc.
|
||||||
bodyFontSize: 12,
|
borderDash: [1, 4],
|
||||||
titleFontSize: 12,
|
color: "#D3D3D3",
|
||||||
|
lineWidth: 0.25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
type: "time",
|
||||||
|
// time: {
|
||||||
|
// unit: 'second'
|
||||||
|
// },
|
||||||
|
distribution: "linear",
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: false,
|
||||||
|
fontSize: 10,
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 10,
|
||||||
|
},
|
||||||
|
// gridLines: false, --> not a valid option
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
callbacks: {
|
GraphTracePopUp = () => {
|
||||||
label: function(tooltipItem, data) {
|
if (this.state.showpopUp) {
|
||||||
|
return (
|
||||||
|
<ChartPopUpUnique
|
||||||
|
xcoordinate={this.state.xcoordinate}
|
||||||
|
ycoordinate={this.state.ycoordinate}
|
||||||
|
>
|
||||||
|
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
||||||
|
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||||
|
</ChartPopUpUnique>
|
||||||
|
);
|
||||||
|
} else return null;
|
||||||
|
};
|
||||||
|
|
||||||
if (typeof(tooltipItem.yLabel) === 'number')
|
render() {
|
||||||
{
|
const ndata = this.props.data;
|
||||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
const data_chartJS = (canvas: any) => {
|
||||||
},
|
const ctx = canvas.getContext("2d");
|
||||||
},
|
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
||||||
|
gradient.addColorStop(0, "rgba(250,174,50,1)");
|
||||||
scales: {
|
gradient.addColorStop(1, "rgba(250,174,50,1)");
|
||||||
yAxes: [
|
return {
|
||||||
{
|
labels: ndata.map((s) => new Date(s.timestamp / 1000000)),
|
||||||
stacked: false,
|
datasets: [
|
||||||
ticks: {
|
{
|
||||||
beginAtZero: false,
|
label: "Request per sec",
|
||||||
fontSize: 10,
|
data: ndata.map((s) => s.callRate),
|
||||||
autoSkip: true,
|
pointRadius: 0.5,
|
||||||
maxTicksLimit: 6,
|
borderColor: "rgba(250,174,50,1)", // Can also add transparency in border color
|
||||||
},
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
gridLines: {
|
};
|
||||||
// You can change the color, the dash effect, the main axe color, etc.
|
};
|
||||||
borderDash: [1, 4],
|
|
||||||
color: "#D3D3D3",
|
|
||||||
lineWidth: 0.25,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
xAxes: [{
|
|
||||||
type: 'time',
|
|
||||||
// time: {
|
|
||||||
// unit: 'second'
|
|
||||||
// },
|
|
||||||
distribution:'linear',
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false,
|
|
||||||
fontSize: 10,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 10,
|
|
||||||
},
|
|
||||||
// gridLines: false, --> not a valid option
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphTracePopUp = () => {
|
|
||||||
|
|
||||||
if (this.state.showpopUp){
|
|
||||||
return(
|
|
||||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
|
||||||
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
|
||||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
|
||||||
</ChartPopUpUnique>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
render(){
|
|
||||||
|
|
||||||
const ndata = this.props.data;
|
|
||||||
|
|
||||||
const data_chartJS = (canvas:any) => {
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
|
||||||
gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
|
||||||
gradient.addColorStop(1, 'rgba(250,174,50,1)');
|
|
||||||
return{labels: ndata.map(s => new Date(s.timestamp/1000000)),
|
|
||||||
datasets: [{
|
|
||||||
label: 'Request per sec',
|
|
||||||
data: ndata.map(s => s.callRate),
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(250,174,50,1)', // Can also add transparency in border color
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
]}
|
|
||||||
};
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
{this.GraphTracePopUp()}
|
|
||||||
<ChartJSLine ref={this.chartRef} data={data_chartJS} options={this.options_charts} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.GraphTracePopUp()}
|
||||||
|
<ChartJSLine
|
||||||
|
ref={this.chartRef}
|
||||||
|
data={data_chartJS}
|
||||||
|
options={this.options_charts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(RequestRateChart);
|
export default withRouter(RequestRateChart);
|
||||||
|
@ -1,103 +1,122 @@
|
|||||||
import React,{useEffect} from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { Tabs, Card, Row, Col} from 'antd';
|
import { Tabs, Card, Row, Col } from "antd";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import { useParams, RouteComponentProps } from "react-router-dom";
|
import { useParams, RouteComponentProps } from "react-router-dom";
|
||||||
import { withRouter } from "react-router";
|
import { withRouter } from "react-router";
|
||||||
|
|
||||||
import { getServicesMetrics, metricItem, getTopEndpoints, topEndpointListItem, GlobalTime, updateTimeInterval } from '../../actions';
|
import {
|
||||||
import { StoreState } from '../../reducers'
|
getServicesMetrics,
|
||||||
import LatencyLineChart from "./LatencyLineChart"
|
metricItem,
|
||||||
import RequestRateChart from './RequestRateChart'
|
getTopEndpoints,
|
||||||
import ErrorRateChart from './ErrorRateChart'
|
topEndpointListItem,
|
||||||
import TopEndpointsTable from './TopEndpointsTable';
|
GlobalTime,
|
||||||
|
updateTimeInterval,
|
||||||
|
} from "../../actions";
|
||||||
|
import { StoreState } from "../../reducers";
|
||||||
|
import LatencyLineChart from "./LatencyLineChart";
|
||||||
|
import RequestRateChart from "./RequestRateChart";
|
||||||
|
import ErrorRateChart from "./ErrorRateChart";
|
||||||
|
import TopEndpointsTable from "./TopEndpointsTable";
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
interface ServicesMetricsProps extends RouteComponentProps<any>{
|
interface ServicesMetricsProps extends RouteComponentProps<any> {
|
||||||
serviceMetrics: metricItem[],
|
serviceMetrics: metricItem[];
|
||||||
getServicesMetrics: Function,
|
getServicesMetrics: Function;
|
||||||
topEndpointsList: topEndpointListItem[],
|
topEndpointsList: topEndpointListItem[];
|
||||||
getTopEndpoints: Function,
|
getTopEndpoints: Function;
|
||||||
globalTime: GlobalTime,
|
globalTime: GlobalTime;
|
||||||
updateTimeInterval: Function,
|
updateTimeInterval: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const _ServiceMetrics = (props: ServicesMetricsProps) => {
|
const _ServiceMetrics = (props: ServicesMetricsProps) => {
|
||||||
|
const params = useParams<{ servicename?: string }>();
|
||||||
|
|
||||||
const params = useParams<{ servicename?: string; }>();
|
useEffect(() => {
|
||||||
|
props.getServicesMetrics(params.servicename, props.globalTime);
|
||||||
|
props.getTopEndpoints(params.servicename, props.globalTime);
|
||||||
|
}, [props.globalTime, params.servicename]);
|
||||||
|
|
||||||
useEffect( () => {
|
const onTracePopupClick = (timestamp: number) => {
|
||||||
props.getServicesMetrics(params.servicename,props.globalTime);
|
props.updateTimeInterval("custom", [
|
||||||
props.getTopEndpoints(params.servicename,props.globalTime);
|
timestamp / 1000000 - 5 * 60 * 1000,
|
||||||
}, [props.globalTime,params.servicename]);
|
timestamp / 1000000,
|
||||||
|
]); // updateTimeInterval takes second range in ms -- give -5 min to selected time,
|
||||||
const onTracePopupClick = (timestamp:number) => {
|
props.history.push("/traces");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Tabs defaultActiveKey="1">
|
||||||
|
<TabPane tab="Application Metrics" key="1">
|
||||||
|
<Row gutter={32} style={{ margin: 20 }}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card bodyStyle={{ padding: 10 }}>
|
||||||
|
<LatencyLineChart
|
||||||
|
data={props.serviceMetrics}
|
||||||
|
popupClickHandler={onTracePopupClick}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
props.updateTimeInterval('custom',[(timestamp/1000000)-5*60*1000,(timestamp/1000000)])// updateTimeInterval takes second range in ms -- give -5 min to selected time,
|
<Col span={12}>
|
||||||
props.history.push('/traces')
|
<Card bodyStyle={{ padding: 10 }}>
|
||||||
}
|
<RequestRateChart data={props.serviceMetrics} />
|
||||||
return (
|
</Card>
|
||||||
<Tabs defaultActiveKey="1">
|
</Col>
|
||||||
<TabPane tab="Application Metrics" key="1">
|
</Row>
|
||||||
|
|
||||||
<Row gutter={32} style={{ margin: 20 }}>
|
<Row gutter={32} style={{ margin: 20 }}>
|
||||||
<Col span={12} >
|
<Col span={12}>
|
||||||
<Card bodyStyle={{padding:10}}>
|
<Card bodyStyle={{ padding: 10 }}>
|
||||||
<LatencyLineChart data={props.serviceMetrics} popupClickHandler={onTracePopupClick} />
|
<ErrorRateChart data={props.serviceMetrics} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Card bodyStyle={{padding:10}}>
|
<Card bodyStyle={{ padding: 10 }}>
|
||||||
<RequestRateChart data={props.serviceMetrics} />
|
<TopEndpointsTable data={props.topEndpointsList} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
<Row gutter={32} style={{ margin: 20 }}>
|
<TabPane tab="External Calls" key="2">
|
||||||
<Col span={12}>
|
<div style={{ margin: 20 }}> Coming Soon.. </div>
|
||||||
<Card bodyStyle={{padding:10}}>
|
<div
|
||||||
<ErrorRateChart data={props.serviceMetrics} />
|
className="container"
|
||||||
</Card>
|
style={{ display: "flex", flexFlow: "column wrap" }}
|
||||||
</Col>
|
>
|
||||||
|
<div className="row">
|
||||||
<Col span={12}>
|
<div className="col-md-6 col-sm-12 col-12">
|
||||||
<Card bodyStyle={{padding:10}}>
|
{/* <ChartJSLineChart data={this.state.graphData} /> */}
|
||||||
<TopEndpointsTable data={props.topEndpointsList} />
|
</div>
|
||||||
</Card>
|
<div className="col-md-6 col-sm-12 col-12">
|
||||||
</Col>
|
{/* <ChartJSLineChart data={this.state.graphData} /> */}
|
||||||
</Row>
|
</div>
|
||||||
</TabPane>
|
</div>
|
||||||
|
</div>
|
||||||
<TabPane tab="External Calls" key="2">
|
</TabPane>
|
||||||
<div style={{ margin: 20 }}> Coming Soon.. </div>
|
</Tabs>
|
||||||
<div className="container" style={{ display: 'flex', flexFlow: 'column wrap' }}>
|
);
|
||||||
|
|
||||||
<div className='row'>
|
|
||||||
<div className='col-md-6 col-sm-12 col-12'>
|
|
||||||
|
|
||||||
{/* <ChartJSLineChart data={this.state.graphData} /> */}
|
|
||||||
</div>
|
|
||||||
<div className='col-md-6 col-sm-12 col-12'>
|
|
||||||
{/* <ChartJSLineChart data={this.state.graphData} /> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { serviceMetrics: metricItem[], topEndpointsList: topEndpointListItem[],globalTime: GlobalTime} => {
|
|
||||||
|
|
||||||
return { serviceMetrics : state.serviceMetrics, topEndpointsList: state.topEndpointsList, globalTime:state.globalTime};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServiceMetrics = withRouter(connect(mapStateToProps, {
|
const mapStateToProps = (
|
||||||
getServicesMetrics: getServicesMetrics,
|
state: StoreState,
|
||||||
getTopEndpoints: getTopEndpoints,
|
): {
|
||||||
updateTimeInterval: updateTimeInterval,
|
serviceMetrics: metricItem[];
|
||||||
|
topEndpointsList: topEndpointListItem[];
|
||||||
})(_ServiceMetrics));
|
globalTime: GlobalTime;
|
||||||
|
} => {
|
||||||
|
return {
|
||||||
|
serviceMetrics: state.serviceMetrics,
|
||||||
|
topEndpointsList: state.topEndpointsList,
|
||||||
|
globalTime: state.globalTime,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ServiceMetrics = withRouter(
|
||||||
|
connect(mapStateToProps, {
|
||||||
|
getServicesMetrics: getServicesMetrics,
|
||||||
|
getTopEndpoints: getTopEndpoints,
|
||||||
|
updateTimeInterval: updateTimeInterval,
|
||||||
|
})(_ServiceMetrics),
|
||||||
|
);
|
||||||
|
@ -1 +1 @@
|
|||||||
export { ServiceMetrics as default } from './ServiceMetrics';
|
export { ServiceMetrics as default } from "./ServiceMetrics";
|
||||||
|
@ -1,90 +1,97 @@
|
|||||||
import React, {useEffect} from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from "react-router-dom";
|
||||||
import { Table } from 'antd';
|
import { Table } from "antd";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import { getServicesList, GlobalTime, servicesListItem } from "../../actions";
|
||||||
import { getServicesList, GlobalTime, servicesListItem } from '../../actions';
|
import { StoreState } from "../../reducers";
|
||||||
import { StoreState } from '../../reducers'
|
|
||||||
|
|
||||||
interface ServicesTableProps {
|
interface ServicesTableProps {
|
||||||
servicesList: servicesListItem[],
|
servicesList: servicesListItem[];
|
||||||
getServicesList: Function,
|
getServicesList: Function;
|
||||||
globalTime: GlobalTime,
|
globalTime: GlobalTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
padding-top:40px;
|
padding-top: 40px;
|
||||||
padding-bottom:40px;
|
padding-bottom: 40px;
|
||||||
padding-left:40px;
|
padding-left: 40px;
|
||||||
padding-right:40px;
|
padding-right: 40px;
|
||||||
.ant-table table { font-size: 12px; };
|
.ant-table table {
|
||||||
.ant-table tfoot>tr>td, .ant-table tfoot>tr>th, .ant-table-tbody>tr>td, .ant-table-thead>tr>th { padding: 10px; };
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.ant-table tfoot > tr > td,
|
||||||
|
.ant-table tfoot > tr > th,
|
||||||
|
.ant-table-tbody > tr > td,
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
{
|
||||||
{
|
title: "Application",
|
||||||
title: 'Application',
|
dataIndex: "serviceName",
|
||||||
dataIndex: 'serviceName',
|
key: "serviceName",
|
||||||
key: 'serviceName',
|
render: (text: string) => (
|
||||||
render: (text :string) => <NavLink style={{textTransform:'capitalize'}} to={'/application/' + text}><strong>{text}</strong></NavLink>,
|
<NavLink style={{ textTransform: "capitalize" }} to={"/application/" + text}>
|
||||||
},
|
<strong>{text}</strong>
|
||||||
{
|
</NavLink>
|
||||||
title: 'P99 latency (in ms)',
|
),
|
||||||
dataIndex: 'p99',
|
},
|
||||||
key: 'p99',
|
{
|
||||||
sorter: (a:any, b:any) => a.p99 - b.p99,
|
title: "P99 latency (in ms)",
|
||||||
// sortDirections: ['descend', 'ascend'],
|
dataIndex: "p99",
|
||||||
render: (value: number) => (value/1000000).toFixed(2),
|
key: "p99",
|
||||||
},
|
sorter: (a: any, b: any) => a.p99 - b.p99,
|
||||||
{
|
// sortDirections: ['descend', 'ascend'],
|
||||||
title: 'Error Rate (in %)',
|
render: (value: number) => (value / 1000000).toFixed(2),
|
||||||
dataIndex: 'errorRate',
|
},
|
||||||
key: 'errorRate',
|
{
|
||||||
sorter: (a:any, b:any) => a.errorRate - b.errorRate,
|
title: "Error Rate (in %)",
|
||||||
// sortDirections: ['descend', 'ascend'],
|
dataIndex: "errorRate",
|
||||||
render: (value: number) => (value*100).toFixed(2),
|
key: "errorRate",
|
||||||
},
|
sorter: (a: any, b: any) => a.errorRate - b.errorRate,
|
||||||
{
|
// sortDirections: ['descend', 'ascend'],
|
||||||
title: 'Requests Per Second',
|
render: (value: number) => (value * 100).toFixed(2),
|
||||||
dataIndex: 'callRate',
|
},
|
||||||
key: 'callRate',
|
{
|
||||||
sorter: (a:any, b:any) => a.callRate - b.callRate,
|
title: "Requests Per Second",
|
||||||
// sortDirections: ['descend', 'ascend'],
|
dataIndex: "callRate",
|
||||||
render: (value: number) => value.toFixed(2),
|
key: "callRate",
|
||||||
},
|
sorter: (a: any, b: any) => a.callRate - b.callRate,
|
||||||
|
// sortDirections: ['descend', 'ascend'],
|
||||||
|
render: (value: number) => value.toFixed(2),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const _ServicesTable = (props: ServicesTableProps) => {
|
const _ServicesTable = (props: ServicesTableProps) => {
|
||||||
|
const search = useLocation().search;
|
||||||
|
const time_interval = new URLSearchParams(search).get("time");
|
||||||
|
|
||||||
const search = useLocation().search;
|
useEffect(() => {
|
||||||
const time_interval = new URLSearchParams(search).get('time');
|
props.getServicesList(props.globalTime);
|
||||||
|
}, [props.globalTime]);
|
||||||
useEffect( () => {
|
|
||||||
props.getServicesList(props.globalTime);
|
|
||||||
}, [props.globalTime]);
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Table
|
||||||
|
dataSource={props.servicesList}
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return(
|
const mapStateToProps = (
|
||||||
|
state: StoreState,
|
||||||
<Wrapper>
|
): { servicesList: servicesListItem[]; globalTime: GlobalTime } => {
|
||||||
<Table dataSource={props.servicesList} columns={columns} pagination={false} />
|
return { servicesList: state.servicesList, globalTime: state.globalTime };
|
||||||
</Wrapper>
|
};
|
||||||
|
|
||||||
);
|
export const ServicesTable = connect(mapStateToProps, {
|
||||||
|
getServicesList: getServicesList,
|
||||||
}
|
})(_ServicesTable);
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { servicesList: servicesListItem[], globalTime: GlobalTime } => {
|
|
||||||
return { servicesList : state.servicesList, globalTime:state.globalTime};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ServicesTable = connect(mapStateToProps, {
|
|
||||||
getServicesList: getServicesList,
|
|
||||||
})(_ServicesTable);
|
|
||||||
|
@ -1 +1 @@
|
|||||||
export { ServicesTable as default } from './ServicesTable';
|
export { ServicesTable as default } from "./ServicesTable";
|
||||||
|
@ -1,73 +1,76 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from "react-router-dom";
|
||||||
import { Table } from 'antd'
|
import { Table } from "antd";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import { topEndpointListItem } from '../../actions/metrics';
|
import { topEndpointListItem } from "../../actions/metrics";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
padding-top:10px;
|
padding-top: 10px;
|
||||||
padding-bottom:10px;
|
padding-bottom: 10px;
|
||||||
padding-left:20px;
|
padding-left: 20px;
|
||||||
padding-right:20px;
|
padding-right: 20px;
|
||||||
.ant-table table { font-size: 12px; };
|
.ant-table table {
|
||||||
.ant-table tfoot>tr>td, .ant-table tfoot>tr>th, .ant-table-tbody>tr>td, .ant-table-thead>tr>th { padding: 10px; };
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.ant-table tfoot > tr > td,
|
||||||
|
.ant-table tfoot > tr > th,
|
||||||
|
.ant-table-tbody > tr > td,
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface TopEndpointsTableProps {
|
interface TopEndpointsTableProps {
|
||||||
data : topEndpointListItem[],
|
data: topEndpointListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopEndpointsTable = (props: TopEndpointsTableProps) => {
|
const TopEndpointsTable = (props: TopEndpointsTableProps) => {
|
||||||
|
const columns: any = [
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
|
||||||
const columns: any = [
|
render: (text: string) => <NavLink to={"/" + text}>{text}</NavLink>,
|
||||||
{
|
},
|
||||||
title: 'Name',
|
{
|
||||||
dataIndex: 'name',
|
title: "P50 (in ms)",
|
||||||
key: 'name',
|
dataIndex: "p50",
|
||||||
|
key: "p50",
|
||||||
|
sorter: (a: any, b: any) => a.p50 - b.p50,
|
||||||
|
// sortDirections: ['descend', 'ascend'],
|
||||||
|
render: (value: number) => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "P90 (in ms)",
|
||||||
|
dataIndex: "p90",
|
||||||
|
key: "p90",
|
||||||
|
sorter: (a: any, b: any) => a.p90 - b.p90,
|
||||||
|
// sortDirections: ['descend', 'ascend'],
|
||||||
|
render: (value: number) => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "P99 (in ms)",
|
||||||
|
dataIndex: "p99",
|
||||||
|
key: "p99",
|
||||||
|
sorter: (a: any, b: any) => a.p99 - b.p99,
|
||||||
|
// sortDirections: ['descend', 'ascend'],
|
||||||
|
render: (value: number) => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Number of Calls",
|
||||||
|
dataIndex: "numCalls",
|
||||||
|
key: "numCalls",
|
||||||
|
sorter: (a: any, b: any) => a.numCalls - b.numCalls,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
render: (text :string) => <NavLink to={'/' + text}>{text}</NavLink>,
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<h6> Top Endpoints</h6>
|
||||||
|
<Table dataSource={props.data} columns={columns} pagination={false} />
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
},
|
export default TopEndpointsTable;
|
||||||
{
|
|
||||||
title: 'P50 (in ms)',
|
|
||||||
dataIndex: 'p50',
|
|
||||||
key: 'p50',
|
|
||||||
sorter: (a:any, b:any) => a.p50 - b.p50,
|
|
||||||
// sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value/1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'P90 (in ms)',
|
|
||||||
dataIndex: 'p90',
|
|
||||||
key: 'p90',
|
|
||||||
sorter: (a:any, b:any) => a.p90 - b.p90,
|
|
||||||
// sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value/1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'P99 (in ms)',
|
|
||||||
dataIndex: 'p99',
|
|
||||||
key: 'p99',
|
|
||||||
sorter: (a:any, b:any) => a.p99 - b.p99,
|
|
||||||
// sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value/1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Number of Calls',
|
|
||||||
dataIndex: 'numCalls',
|
|
||||||
key: 'numCalls',
|
|
||||||
sorter: (a:any, b:any) => a.numCalls - b.numCalls,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return(
|
|
||||||
<Wrapper>
|
|
||||||
<h6> Top Endpoints</h6>
|
|
||||||
<Table dataSource={props.data} columns={columns} pagination={false} />
|
|
||||||
</Wrapper>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TopEndpointsTable;
|
|
||||||
|
@ -3,69 +3,69 @@ import React from "react";
|
|||||||
import Graph from "react-graph-vis";
|
import Graph from "react-graph-vis";
|
||||||
// import { graphEvents } from "react-graph-vis";
|
// import { graphEvents } from "react-graph-vis";
|
||||||
|
|
||||||
//PNOTE - types of react-graph-vis defined in typings folder.
|
//PNOTE - types of react-graph-vis defined in typings folder.
|
||||||
//How is it imported directly?
|
//How is it imported directly?
|
||||||
// type definition for service graph - https://github.com/crubier/react-graph-vis/issues/80
|
// 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#
|
// Set shapes - https://visjs.github.io/vis-network/docs/network/nodes.html#
|
||||||
// https://github.com/crubier/react-graph-vis/issues/93
|
// https://github.com/crubier/react-graph-vis/issues/93
|
||||||
const graph = {
|
const graph = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{ id: 1, label: "Catalogue", shape: "box", color: "green",border: "black",size: 100 },
|
{
|
||||||
{ id: 2, label: "Users", shape: "box", color: "#FFFF00" },
|
id: 1,
|
||||||
{ id: 3, label: "Payment App", shape: "box", color: "#FB7E81" },
|
label: "Catalogue",
|
||||||
{ id: 4, label: "My Sql", shape: "box", size: 10, color: "#7BE141" },
|
shape: "box",
|
||||||
{ id: 5, label: "Redis-db", shape: "box", color: "#6E6EFD" },
|
color: "green",
|
||||||
],
|
border: "black",
|
||||||
edges: [
|
size: 100,
|
||||||
{from:1,to:2,color: { color: "red" },size:{size:20}},
|
},
|
||||||
{from:2,to:3,color: { color: "red" }},
|
{ id: 2, label: "Users", shape: "box", color: "#FFFF00" },
|
||||||
{from:1,to:3,color: { color: "red" }},
|
{ id: 3, label: "Payment App", shape: "box", color: "#FB7E81" },
|
||||||
{from:3,to:4,color: { color: "red" }},
|
{ id: 4, label: "My Sql", shape: "box", size: 10, color: "#7BE141" },
|
||||||
{from:3,to:5,color: { color: "red" }},
|
{ id: 5, label: "Redis-db", shape: "box", color: "#6E6EFD" },
|
||||||
]
|
],
|
||||||
};
|
edges: [
|
||||||
|
{ from: 1, to: 2, color: { color: "red" }, size: { size: 20 } },
|
||||||
const options = {
|
{ from: 2, to: 3, color: { color: "red" } },
|
||||||
layout: {
|
{ from: 1, to: 3, color: { color: "red" } },
|
||||||
hierarchical: true
|
{ from: 3, to: 4, color: { color: "red" } },
|
||||||
},
|
{ from: 3, to: 5, color: { color: "red" } },
|
||||||
edges: {
|
],
|
||||||
color: "#000000"
|
};
|
||||||
},
|
|
||||||
height: "500px"
|
const options = {
|
||||||
};
|
layout: {
|
||||||
|
hierarchical: true,
|
||||||
// const events = {
|
},
|
||||||
// select: function(event:any) { //PNOTE - TO DO - Get rid of any type
|
edges: {
|
||||||
// var { nodes, edges } = event;
|
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 ServiceGraph = () => {
|
||||||
|
// const [network, setNetwork] = useState(null);
|
||||||
|
|
||||||
// const [network, setNetwork] = useState(null);
|
return (
|
||||||
|
<React.Fragment>
|
||||||
return (
|
<div> Updated Service Graph module coming soon..</div>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
<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;
|
export default ServiceGraph;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ServiceGraph from './ServiceGraph'
|
import ServiceGraph from "./ServiceGraph";
|
||||||
|
|
||||||
const ServiceMap = () => {
|
const ServiceMap = () => {
|
||||||
|
return (
|
||||||
return (
|
<div>
|
||||||
|
{" "}
|
||||||
<div> Service Map module coming soon...
|
Service Map module coming soon...
|
||||||
{/* <ServiceGraph /> */}
|
{/* <ServiceGraph /> */}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default ServiceMap;
|
export default ServiceMap;
|
||||||
|
@ -1,77 +1,115 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Card, Tag } from 'antd';
|
import { Card, Tag } from "antd";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import { StoreState } from "../../reducers";
|
||||||
import { StoreState } from '../../reducers'
|
import { TagItem, TraceFilters, updateTraceFilters } from "../../actions";
|
||||||
import { TagItem, TraceFilters, updateTraceFilters } from '../../actions';
|
|
||||||
|
|
||||||
interface FilterStateDisplayProps {
|
interface FilterStateDisplayProps {
|
||||||
traceFilters: TraceFilters,
|
traceFilters: TraceFilters;
|
||||||
updateTraceFilters: Function,
|
updateTraceFilters: Function;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const _FilterStateDisplay = (props: FilterStateDisplayProps) => {
|
|
||||||
|
|
||||||
function handleCloseTag(value:string) {
|
|
||||||
if (value==='service')
|
|
||||||
props.updateTraceFilters({...props.traceFilters,service:''})
|
|
||||||
if (value==='operation')
|
|
||||||
props.updateTraceFilters({...props.traceFilters,operation:''})
|
|
||||||
if (value==='maxLatency')
|
|
||||||
props.updateTraceFilters({...props.traceFilters,latency:{'max':'','min':props.traceFilters.latency?.min}})
|
|
||||||
if (value==='minLatency')
|
|
||||||
props.updateTraceFilters({...props.traceFilters,latency:{'min':'','max':props.traceFilters.latency?.max}})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCloseTagElement(item:TagItem){
|
|
||||||
props.updateTraceFilters({...props.traceFilters,tags:props.traceFilters.tags?.filter(elem => elem !== item)})
|
|
||||||
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
|
|
||||||
<Card style={{padding: 6, marginTop: 10, marginBottom: 10}} bodyStyle={{padding: 6}}>
|
|
||||||
|
|
||||||
{(props.traceFilters.service===''||props.traceFilters.operation===undefined)? null:
|
|
||||||
<Tag style={{fontSize:14, padding: 8}} closable
|
|
||||||
onClose={e => {handleCloseTag('service');}}>
|
|
||||||
service:{props.traceFilters.service}
|
|
||||||
</Tag> }
|
|
||||||
{(props.traceFilters.operation===''||props.traceFilters.operation===undefined)? null:
|
|
||||||
<Tag style={{fontSize:14, padding: 8}} closable
|
|
||||||
onClose={e => {handleCloseTag('operation');}}>
|
|
||||||
operation:{props.traceFilters.operation}
|
|
||||||
</Tag> }
|
|
||||||
{props.traceFilters.latency===undefined||props.traceFilters.latency?.min===''? null:
|
|
||||||
<Tag style={{fontSize:14, padding: 8}} closable
|
|
||||||
onClose={e => {handleCloseTag('minLatency');}}>
|
|
||||||
minLatency:{(parseInt(props.traceFilters.latency!.min)/1000000).toString()}ms
|
|
||||||
</Tag> }
|
|
||||||
{props.traceFilters.latency===undefined||props.traceFilters.latency?.max===''? null:
|
|
||||||
<Tag style={{fontSize:14, padding: 8}} closable
|
|
||||||
onClose={e => {handleCloseTag('maxLatency');}}>
|
|
||||||
maxLatency:{(parseInt(props.traceFilters.latency!.max)/1000000).toString()}ms
|
|
||||||
</Tag> }
|
|
||||||
{props.traceFilters.tags === undefined? null: props.traceFilters.tags.map( item => (
|
|
||||||
<Tag style={{fontSize:14, padding: 8}} closable
|
|
||||||
onClose={e => {handleCloseTagElement(item);}}>
|
|
||||||
{item.key} {item.operator} {item.value}
|
|
||||||
</Tag>))}
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters } => {
|
const _FilterStateDisplay = (props: FilterStateDisplayProps) => {
|
||||||
return { traceFilters : state.traceFilters };
|
function handleCloseTag(value: string) {
|
||||||
|
if (value === "service")
|
||||||
|
props.updateTraceFilters({ ...props.traceFilters, service: "" });
|
||||||
|
if (value === "operation")
|
||||||
|
props.updateTraceFilters({ ...props.traceFilters, operation: "" });
|
||||||
|
if (value === "maxLatency")
|
||||||
|
props.updateTraceFilters({
|
||||||
|
...props.traceFilters,
|
||||||
|
latency: { max: "", min: props.traceFilters.latency?.min },
|
||||||
|
});
|
||||||
|
if (value === "minLatency")
|
||||||
|
props.updateTraceFilters({
|
||||||
|
...props.traceFilters,
|
||||||
|
latency: { min: "", max: props.traceFilters.latency?.max },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCloseTagElement(item: TagItem) {
|
||||||
|
props.updateTraceFilters({
|
||||||
|
...props.traceFilters,
|
||||||
|
tags: props.traceFilters.tags?.filter((elem) => elem !== item),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
style={{ padding: 6, marginTop: 10, marginBottom: 10 }}
|
||||||
|
bodyStyle={{ padding: 6 }}
|
||||||
|
>
|
||||||
|
{props.traceFilters.service === "" ||
|
||||||
|
props.traceFilters.operation === undefined ? null : (
|
||||||
|
<Tag
|
||||||
|
style={{ fontSize: 14, padding: 8 }}
|
||||||
|
closable
|
||||||
|
onClose={(e) => {
|
||||||
|
handleCloseTag("service");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
service:{props.traceFilters.service}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{props.traceFilters.operation === "" ||
|
||||||
|
props.traceFilters.operation === undefined ? null : (
|
||||||
|
<Tag
|
||||||
|
style={{ fontSize: 14, padding: 8 }}
|
||||||
|
closable
|
||||||
|
onClose={(e) => {
|
||||||
|
handleCloseTag("operation");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
operation:{props.traceFilters.operation}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{props.traceFilters.latency === undefined ||
|
||||||
|
props.traceFilters.latency?.min === "" ? null : (
|
||||||
|
<Tag
|
||||||
|
style={{ fontSize: 14, padding: 8 }}
|
||||||
|
closable
|
||||||
|
onClose={(e) => {
|
||||||
|
handleCloseTag("minLatency");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
minLatency:
|
||||||
|
{(parseInt(props.traceFilters.latency!.min) / 1000000).toString()}ms
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{props.traceFilters.latency === undefined ||
|
||||||
|
props.traceFilters.latency?.max === "" ? null : (
|
||||||
|
<Tag
|
||||||
|
style={{ fontSize: 14, padding: 8 }}
|
||||||
|
closable
|
||||||
|
onClose={(e) => {
|
||||||
|
handleCloseTag("maxLatency");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
maxLatency:
|
||||||
|
{(parseInt(props.traceFilters.latency!.max) / 1000000).toString()}ms
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{props.traceFilters.tags === undefined
|
||||||
|
? null
|
||||||
|
: props.traceFilters.tags.map((item) => (
|
||||||
|
<Tag
|
||||||
|
style={{ fontSize: 14, padding: 8 }}
|
||||||
|
closable
|
||||||
|
onClose={(e) => {
|
||||||
|
handleCloseTagElement(item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.key} {item.operator} {item.value}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters } => {
|
||||||
export const FilterStateDisplay = connect(mapStateToProps,
|
return { traceFilters: state.traceFilters };
|
||||||
{
|
};
|
||||||
updateTraceFilters: updateTraceFilters,
|
|
||||||
|
|
||||||
})(_FilterStateDisplay);
|
|
||||||
|
|
||||||
|
export const FilterStateDisplay = connect(mapStateToProps, {
|
||||||
|
updateTraceFilters: updateTraceFilters,
|
||||||
|
})(_FilterStateDisplay);
|
||||||
|
@ -1,66 +1,65 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Modal, Form, InputNumber, Col, Row} from 'antd';
|
import { Modal, Form, InputNumber, Col, Row } from "antd";
|
||||||
import { Store } from 'antd/lib/form/interface';
|
import { Store } from "antd/lib/form/interface";
|
||||||
|
|
||||||
|
|
||||||
interface LatencyModalFormProps {
|
interface LatencyModalFormProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onCreate: (values: Store) => void; //Store is defined in antd forms library
|
onCreate: (values: Store) => void; //Store is defined in antd forms library
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
||||||
visible,
|
visible,
|
||||||
onCreate,
|
onCreate,
|
||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
title="Chose min and max values of Latency"
|
title="Chose min and max values of Latency"
|
||||||
okText="Apply"
|
okText="Apply"
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
.then(values => {
|
.then((values) => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
onCreate(values); // giving error for values
|
onCreate(values); // giving error for values
|
||||||
})
|
})
|
||||||
.catch(info => {
|
.catch((info) => {
|
||||||
console.log('Validate Failed:', info);
|
console.log("Validate Failed:", info);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="horizontal"
|
layout="horizontal"
|
||||||
name="form_in_modal"
|
name="form_in_modal"
|
||||||
initialValues={{ min: '100', max:'500' }}
|
initialValues={{ min: "100", max: "500" }}
|
||||||
>
|
>
|
||||||
<Row>
|
<Row>
|
||||||
{/* <Input.Group compact> */}
|
{/* <Input.Group compact> */}
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="min"
|
name="min"
|
||||||
label="Min (in ms)"
|
label="Min (in ms)"
|
||||||
// rules={[{ required: true, message: 'Please input the title of collection!' }]}
|
// rules={[{ required: true, message: 'Please input the title of collection!' }]}
|
||||||
>
|
>
|
||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name="max" label="Max (in ms)">
|
<Form.Item name="max" label="Max (in ms)">
|
||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/* </Input.Group> */}
|
{/* </Input.Group> */}
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LatencyModalForm;
|
export default LatencyModalForm;
|
||||||
|
@ -1,46 +1,52 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import {Card,Tabs} from 'antd';
|
import { Card, Tabs } from "antd";
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface spanTagItem {
|
interface spanTagItem {
|
||||||
key:string;
|
key: string;
|
||||||
type:string;
|
type: string;
|
||||||
value:string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectedSpanDetailsProps {
|
interface SelectedSpanDetailsProps {
|
||||||
clickedSpanTags: spanTagItem[]
|
clickedSpanTags: spanTagItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectedSpanDetails = (props: SelectedSpanDetailsProps) => {
|
const SelectedSpanDetails = (props: SelectedSpanDetailsProps) => {
|
||||||
|
const callback = (key: any) => {};
|
||||||
|
|
||||||
const callback = (key:any) => {
|
return (
|
||||||
}
|
<Card style={{ height: 320 }}>
|
||||||
|
<Tabs defaultActiveKey="1" onChange={callback}>
|
||||||
|
<TabPane tab="Tags" key="1">
|
||||||
|
<strong> Details for selected Span </strong>
|
||||||
|
{props.clickedSpanTags.map((tags, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
style={{ color: "grey", fontSize: "13px", listStyle: "none" }}
|
||||||
|
>
|
||||||
|
<span className="mr-1">{tags.key}</span>:
|
||||||
|
<span className="ml-1">
|
||||||
|
{tags.key === "error" ? "true" : tags.value}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}{" "}
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab="Errors" key="2">
|
||||||
|
{props.clickedSpanTags
|
||||||
|
.filter((tags) => tags.key === "error")
|
||||||
|
.map((error) => (
|
||||||
|
<div className="ml-5">
|
||||||
|
<p style={{ color: "grey", fontSize: "10px" }}>
|
||||||
|
<span className="mr-1">{error.key}</span>:
|
||||||
|
<span className="ml-1">true</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
export default SelectedSpanDetails;
|
||||||
|
|
||||||
<Card style={{ height: 320 }} >
|
|
||||||
<Tabs defaultActiveKey="1" onChange={callback}>
|
|
||||||
|
|
||||||
<TabPane tab="Tags" key="1">
|
|
||||||
<strong> Details for selected Span </strong>
|
|
||||||
{props.clickedSpanTags.map((tags,index)=><li key={index} style={{color:'grey',fontSize:'13px',listStyle:'none'}}><span className='mr-1'>{tags.key}</span>:<span className='ml-1'>{tags.key==='error'?"true":tags.value}</span></li>)} </TabPane>
|
|
||||||
<TabPane tab="Errors" key="2">
|
|
||||||
{props.clickedSpanTags.filter(tags=>tags.key==='error').map(
|
|
||||||
error => <div className='ml-5'>
|
|
||||||
<p style={{color:'grey',fontSize:'10px'}}>
|
|
||||||
<span className='mr-1'>{error.key}</span>:
|
|
||||||
<span className='ml-1'>true</span></p>
|
|
||||||
</div>)}
|
|
||||||
</TabPane>
|
|
||||||
|
|
||||||
</Tabs>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SelectedSpanDetails;
|
|
||||||
|
@ -1,221 +1,238 @@
|
|||||||
import React, {useState,useEffect} from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import GenericVisualizations from '../metrics/GenericVisualization'
|
import GenericVisualizations from "../metrics/GenericVisualization";
|
||||||
import {Select, Card, Space, Form} from 'antd';
|
import { Select, Card, Space, Form } from "antd";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { StoreState } from '../../reducers'
|
import { StoreState } from "../../reducers";
|
||||||
import {customMetricsItem, getFilteredTraceMetrics, GlobalTime, TraceFilters} from '../../actions';
|
import {
|
||||||
|
customMetricsItem,
|
||||||
|
getFilteredTraceMetrics,
|
||||||
|
GlobalTime,
|
||||||
|
TraceFilters,
|
||||||
|
} from "../../actions";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const entity = [
|
const entity = [
|
||||||
{
|
{
|
||||||
title: 'Calls',
|
title: "Calls",
|
||||||
key:'calls',
|
key: "calls",
|
||||||
dataindex:'calls'
|
dataindex: "calls",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Duration',
|
title: "Duration",
|
||||||
key:'duration',
|
key: "duration",
|
||||||
dataindex:'duration'
|
dataindex: "duration",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Error',
|
title: "Error",
|
||||||
key:'error',
|
key: "error",
|
||||||
dataindex:'error'
|
dataindex: "error",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Status Code',
|
title: "Status Code",
|
||||||
key:'status_code',
|
key: "status_code",
|
||||||
dataindex:'status_code'
|
dataindex: "status_code",
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const aggregation_options = [
|
const aggregation_options = [
|
||||||
{
|
{
|
||||||
linked_entity: 'calls',
|
linked_entity: "calls",
|
||||||
default_selected:{title:'Count', dataindex:'count'},
|
default_selected: { title: "Count", dataindex: "count" },
|
||||||
options_available: [ {title:'Count', dataindex:'count'}, {title:'Rate (per sec)', dataindex:'rate_per_sec'}]
|
options_available: [
|
||||||
},
|
{ title: "Count", dataindex: "count" },
|
||||||
{
|
{ title: "Rate (per sec)", dataindex: "rate_per_sec" },
|
||||||
linked_entity: 'duration',
|
],
|
||||||
default_selected:{title:'p99', dataindex:'p99'},
|
},
|
||||||
// options_available: [ {title:'Avg', dataindex:'avg'}, {title:'Max', dataindex:'max'},{title:'Min', dataindex:'min'}, {title:'p50', dataindex:'p50'},{title:'p90', dataindex:'p90'}, {title:'p95', dataindex:'p95'}]
|
{
|
||||||
options_available: [ {title:'p50', dataindex:'p50'},{title:'p90', dataindex:'p90'}, {title:'p99', dataindex:'p99'}]
|
linked_entity: "duration",
|
||||||
},
|
default_selected: { title: "p99", dataindex: "p99" },
|
||||||
{
|
// options_available: [ {title:'Avg', dataindex:'avg'}, {title:'Max', dataindex:'max'},{title:'Min', dataindex:'min'}, {title:'p50', dataindex:'p50'},{title:'p90', dataindex:'p90'}, {title:'p95', dataindex:'p95'}]
|
||||||
linked_entity: 'error',
|
options_available: [
|
||||||
default_selected:{title:'Count', dataindex:'count'},
|
{ title: "p50", dataindex: "p50" },
|
||||||
options_available: [ {title:'Count', dataindex:'count'}, {title:'Rate (per sec)', dataindex:'rate_per_sec'}]
|
{ title: "p90", dataindex: "p90" },
|
||||||
},
|
{ title: "p99", dataindex: "p99" },
|
||||||
{
|
],
|
||||||
linked_entity: 'status_code',
|
},
|
||||||
default_selected: {title:'Count', dataindex:'count'},
|
{
|
||||||
options_available: [ {title:'Count', dataindex:'count'}]
|
linked_entity: "error",
|
||||||
},
|
default_selected: { title: "Count", dataindex: "count" },
|
||||||
|
options_available: [
|
||||||
|
{ title: "Count", dataindex: "count" },
|
||||||
|
{ title: "Rate (per sec)", dataindex: "rate_per_sec" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
linked_entity: "status_code",
|
||||||
|
default_selected: { title: "Count", dataindex: "count" },
|
||||||
|
options_available: [{ title: "Count", dataindex: "count" }],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
interface TraceCustomVisualizationsProps {
|
interface TraceCustomVisualizationsProps {
|
||||||
filteredTraceMetrics: customMetricsItem[],
|
filteredTraceMetrics: customMetricsItem[];
|
||||||
globalTime: GlobalTime,
|
globalTime: GlobalTime;
|
||||||
getFilteredTraceMetrics: Function,
|
getFilteredTraceMetrics: Function;
|
||||||
traceFilters: TraceFilters,
|
traceFilters: TraceFilters;
|
||||||
}
|
|
||||||
|
|
||||||
const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
|
|
||||||
|
|
||||||
const [selectedEntity, setSelectedEntity] = useState('calls');
|
|
||||||
const [selectedAggOption, setSelectedAggOption] = useState('count');
|
|
||||||
const [selectedStep, setSelectedStep] = useState('60');
|
|
||||||
// Step should be multiples of 60, 60 -> 1 min
|
|
||||||
|
|
||||||
|
|
||||||
useEffect( () => {
|
|
||||||
|
|
||||||
let request_string= 'service='+props.traceFilters.service+
|
|
||||||
'&operation='+props.traceFilters.operation+
|
|
||||||
'&maxDuration='+props.traceFilters.latency?.max+
|
|
||||||
'&minDuration='+props.traceFilters.latency?.min
|
|
||||||
if(props.traceFilters.tags)
|
|
||||||
request_string=request_string+'&tags='+encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
|
||||||
if(selectedEntity)
|
|
||||||
request_string=request_string+'&dimension='+selectedEntity.toLowerCase();
|
|
||||||
if(selectedAggOption)
|
|
||||||
request_string=request_string+'&aggregation_option='+selectedAggOption.toLowerCase();
|
|
||||||
if(selectedStep)
|
|
||||||
request_string=request_string+'&step='+selectedStep;
|
|
||||||
|
|
||||||
|
|
||||||
props.getFilteredTraceMetrics(request_string, props.globalTime)
|
|
||||||
|
|
||||||
}, [selectedEntity,selectedAggOption,props.traceFilters, props.globalTime ]);
|
|
||||||
|
|
||||||
//Custom metrics API called if time, tracefilters, selected entity or agg option changes
|
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
function handleChange(value:string) {
|
|
||||||
// console.log(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFinish(value:string) {
|
|
||||||
// console.log(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// PNOTE - Can also use 'coordinate' option in antd Select for implementing this - https://ant.design/components/select/
|
|
||||||
const handleFormValuesChange = (changedValues:any) => {
|
|
||||||
const formFieldName = Object.keys(changedValues)[0];
|
|
||||||
if (formFieldName === 'entity') {
|
|
||||||
|
|
||||||
const temp_entity = aggregation_options.filter((item) => item.linked_entity === changedValues[formFieldName])[0];
|
|
||||||
|
|
||||||
form.setFieldsValue( {
|
|
||||||
agg_options : temp_entity.default_selected.title,
|
|
||||||
// PNOTE - TO DO Check if this has the same behaviour as selecting an option?
|
|
||||||
})
|
|
||||||
|
|
||||||
let temp = form.getFieldsValue(['agg_options','entity']);
|
|
||||||
|
|
||||||
setSelectedEntity(temp.entity);
|
|
||||||
setSelectedAggOption(temp.agg_options);
|
|
||||||
//form.setFieldsValue({ agg_options: aggregation_options.filter( item => item.linked_entity === selectedEntity )[0] }); //reset product selection
|
|
||||||
// PNOTE - https://stackoverflow.com/questions/64377293/update-select-option-list-based-on-other-select-field-selection-ant-design
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formFieldName === 'agg_options') {
|
|
||||||
|
|
||||||
setSelectedAggOption(changedValues[formFieldName]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
{/* <Space direction="vertical"> */}
|
|
||||||
<div>Custom Visualizations</div>
|
|
||||||
|
|
||||||
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
onFinish={handleFinish}
|
|
||||||
onValuesChange={handleFormValuesChange}
|
|
||||||
initialValues={{ agg_options: 'Count', chart_style:'line', interval:'5m', group_by:'none' }}
|
|
||||||
>
|
|
||||||
<Space>
|
|
||||||
<Form.Item name="entity">
|
|
||||||
|
|
||||||
<Select defaultValue={selectedEntity} style={{ width: 120 }} allowClear>
|
|
||||||
{entity.map((item) => (
|
|
||||||
<Option key={item.key} value={item.dataindex}>
|
|
||||||
{item.title}
|
|
||||||
</Option>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item name="agg_options">
|
|
||||||
|
|
||||||
<Select style={{ width: 120 }} allowClear>
|
|
||||||
{ aggregation_options.filter((item) => item.linked_entity === selectedEntity)[0].options_available
|
|
||||||
.map((item) => (
|
|
||||||
<Option key={item.dataindex} value={item.dataindex}>
|
|
||||||
{item.title}
|
|
||||||
</Option>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item name="chart_style">
|
|
||||||
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
|
||||||
<Option value="line">Line Chart</Option>
|
|
||||||
<Option value="bar">Bar Chart</Option>
|
|
||||||
<Option value="area">Area Chart</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item name="interval">
|
|
||||||
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
|
||||||
<Option value="1m">1 min</Option>
|
|
||||||
<Option value="5m">5 min</Option>
|
|
||||||
<Option value="30m">30 min</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{/* Need heading for each option */}
|
|
||||||
<Form.Item name="group_by">
|
|
||||||
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
|
||||||
<Option value="none">Group By</Option>
|
|
||||||
<Option value="status">Status Code</Option>
|
|
||||||
<Option value="protocol">Protocol</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Space>
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
|
|
||||||
<GenericVisualizations chartType='line' data={props.filteredTraceMetrics}/>
|
|
||||||
{/* This component should take bar or line as an input */}
|
|
||||||
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { filteredTraceMetrics: customMetricsItem[] , globalTime: GlobalTime, traceFilters: TraceFilters} => {
|
const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
|
||||||
return { filteredTraceMetrics : state.filteredTraceMetrics, globalTime: state.globalTime,traceFilters:state.traceFilters };
|
const [selectedEntity, setSelectedEntity] = useState("calls");
|
||||||
};
|
const [selectedAggOption, setSelectedAggOption] = useState("count");
|
||||||
|
const [selectedStep, setSelectedStep] = useState("60");
|
||||||
|
// Step should be multiples of 60, 60 -> 1 min
|
||||||
export const TraceCustomVisualizations = connect(mapStateToProps, {
|
|
||||||
getFilteredTraceMetrics: getFilteredTraceMetrics,
|
useEffect(() => {
|
||||||
})(_TraceCustomVisualizations);
|
let request_string =
|
||||||
|
"service=" +
|
||||||
|
props.traceFilters.service +
|
||||||
|
"&operation=" +
|
||||||
|
props.traceFilters.operation +
|
||||||
|
"&maxDuration=" +
|
||||||
|
props.traceFilters.latency?.max +
|
||||||
|
"&minDuration=" +
|
||||||
|
props.traceFilters.latency?.min;
|
||||||
|
if (props.traceFilters.tags)
|
||||||
|
request_string =
|
||||||
|
request_string +
|
||||||
|
"&tags=" +
|
||||||
|
encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
||||||
|
if (selectedEntity)
|
||||||
|
request_string =
|
||||||
|
request_string + "&dimension=" + selectedEntity.toLowerCase();
|
||||||
|
if (selectedAggOption)
|
||||||
|
request_string =
|
||||||
|
request_string + "&aggregation_option=" + selectedAggOption.toLowerCase();
|
||||||
|
if (selectedStep) request_string = request_string + "&step=" + selectedStep;
|
||||||
|
|
||||||
|
props.getFilteredTraceMetrics(request_string, props.globalTime);
|
||||||
|
}, [selectedEntity, selectedAggOption, props.traceFilters, props.globalTime]);
|
||||||
|
|
||||||
|
//Custom metrics API called if time, tracefilters, selected entity or agg option changes
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
function handleChange(value: string) {
|
||||||
|
// console.log(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFinish(value: string) {
|
||||||
|
// console.log(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PNOTE - Can also use 'coordinate' option in antd Select for implementing this - https://ant.design/components/select/
|
||||||
|
const handleFormValuesChange = (changedValues: any) => {
|
||||||
|
const formFieldName = Object.keys(changedValues)[0];
|
||||||
|
if (formFieldName === "entity") {
|
||||||
|
const temp_entity = aggregation_options.filter(
|
||||||
|
(item) => item.linked_entity === changedValues[formFieldName],
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
form.setFieldsValue({
|
||||||
|
agg_options: temp_entity.default_selected.title,
|
||||||
|
// PNOTE - TO DO Check if this has the same behaviour as selecting an option?
|
||||||
|
});
|
||||||
|
|
||||||
|
let temp = form.getFieldsValue(["agg_options", "entity"]);
|
||||||
|
|
||||||
|
setSelectedEntity(temp.entity);
|
||||||
|
setSelectedAggOption(temp.agg_options);
|
||||||
|
//form.setFieldsValue({ agg_options: aggregation_options.filter( item => item.linked_entity === selectedEntity )[0] }); //reset product selection
|
||||||
|
// PNOTE - https://stackoverflow.com/questions/64377293/update-select-option-list-based-on-other-select-field-selection-ant-design
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formFieldName === "agg_options") {
|
||||||
|
setSelectedAggOption(changedValues[formFieldName]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
{/* <Space direction="vertical"> */}
|
||||||
|
<div>Custom Visualizations</div>
|
||||||
|
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
onFinish={handleFinish}
|
||||||
|
onValuesChange={handleFormValuesChange}
|
||||||
|
initialValues={{
|
||||||
|
agg_options: "Count",
|
||||||
|
chart_style: "line",
|
||||||
|
interval: "5m",
|
||||||
|
group_by: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<Form.Item name="entity">
|
||||||
|
<Select defaultValue={selectedEntity} style={{ width: 120 }} allowClear>
|
||||||
|
{entity.map((item) => (
|
||||||
|
<Option key={item.key} value={item.dataindex}>
|
||||||
|
{item.title}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="agg_options">
|
||||||
|
<Select style={{ width: 120 }} allowClear>
|
||||||
|
{aggregation_options
|
||||||
|
.filter((item) => item.linked_entity === selectedEntity)[0]
|
||||||
|
.options_available.map((item) => (
|
||||||
|
<Option key={item.dataindex} value={item.dataindex}>
|
||||||
|
{item.title}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="chart_style">
|
||||||
|
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
||||||
|
<Option value="line">Line Chart</Option>
|
||||||
|
<Option value="bar">Bar Chart</Option>
|
||||||
|
<Option value="area">Area Chart</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="interval">
|
||||||
|
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
||||||
|
<Option value="1m">1 min</Option>
|
||||||
|
<Option value="5m">5 min</Option>
|
||||||
|
<Option value="30m">30 min</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* Need heading for each option */}
|
||||||
|
<Form.Item name="group_by">
|
||||||
|
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
||||||
|
<Option value="none">Group By</Option>
|
||||||
|
<Option value="status">Status Code</Option>
|
||||||
|
<Option value="protocol">Protocol</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<GenericVisualizations chartType="line" data={props.filteredTraceMetrics} />
|
||||||
|
{/* This component should take bar or line as an input */}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (
|
||||||
|
state: StoreState,
|
||||||
|
): {
|
||||||
|
filteredTraceMetrics: customMetricsItem[];
|
||||||
|
globalTime: GlobalTime;
|
||||||
|
traceFilters: TraceFilters;
|
||||||
|
} => {
|
||||||
|
return {
|
||||||
|
filteredTraceMetrics: state.filteredTraceMetrics,
|
||||||
|
globalTime: state.globalTime,
|
||||||
|
traceFilters: state.traceFilters,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TraceCustomVisualizations = connect(mapStateToProps, {
|
||||||
|
getFilteredTraceMetrics: getFilteredTraceMetrics,
|
||||||
|
})(_TraceCustomVisualizations);
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import {TraceCustomVisualizations} from './TraceCustomVisualizations';
|
import { TraceCustomVisualizations } from "./TraceCustomVisualizations";
|
||||||
import { TraceFilter } from './TraceFilter';
|
import { TraceFilter } from "./TraceFilter";
|
||||||
import { TraceList } from './TraceList';
|
import { TraceList } from "./TraceList";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const TraceDetail = () => {
|
const TraceDetail = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TraceFilter />
|
||||||
|
<TraceCustomVisualizations />
|
||||||
|
<TraceList />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
export default TraceDetail;
|
||||||
|
|
||||||
<div>
|
|
||||||
<TraceFilter />
|
|
||||||
<TraceCustomVisualizations />
|
|
||||||
<TraceList />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TraceDetail;
|
|
||||||
|
@ -1,293 +1,393 @@
|
|||||||
import React,{useEffect, useState} from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { Select, Button, Input, Form, AutoComplete} from 'antd';
|
import { Select, Button, Input, Form, AutoComplete } from "antd";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import { Store } from 'antd/lib/form/interface';
|
import { Store } from "antd/lib/form/interface";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { updateTraceFilters, fetchTraces, TraceFilters, GlobalTime } from '../../actions';
|
import {
|
||||||
import { StoreState } from '../../reducers';
|
updateTraceFilters,
|
||||||
import LatencyModalForm from './LatencyModalForm';
|
fetchTraces,
|
||||||
import {FilterStateDisplay} from './FilterStateDisplay';
|
TraceFilters,
|
||||||
|
GlobalTime,
|
||||||
|
} from "../../actions";
|
||||||
|
import { StoreState } from "../../reducers";
|
||||||
|
import LatencyModalForm from "./LatencyModalForm";
|
||||||
|
import { FilterStateDisplay } from "./FilterStateDisplay";
|
||||||
|
|
||||||
|
import FormItem from "antd/lib/form/FormItem";
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
import metricsAPI from "../../api/metricsAPI";
|
||||||
import metricsAPI from '../../api/metricsAPI';
|
|
||||||
|
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
const InfoWrapper = styled.div`
|
const InfoWrapper = styled.div`
|
||||||
padding-top:10px;
|
padding-top: 10px;
|
||||||
font-style:italic;
|
font-style: italic;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface TraceFilterProps {
|
interface TraceFilterProps {
|
||||||
traceFilters: TraceFilters,
|
traceFilters: TraceFilters;
|
||||||
globalTime: GlobalTime,
|
globalTime: GlobalTime;
|
||||||
updateTraceFilters: Function,
|
updateTraceFilters: Function;
|
||||||
fetchTraces: Function,
|
fetchTraces: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagKeyOptionItem {
|
interface TagKeyOptionItem {
|
||||||
"tagKeys": string;
|
tagKeys: string;
|
||||||
"tagCount": number;
|
tagCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _TraceFilter = (props: TraceFilterProps) => {
|
const _TraceFilter = (props: TraceFilterProps) => {
|
||||||
|
const [serviceList, setServiceList] = useState<string[]>([]);
|
||||||
|
const [operationList, setOperationsList] = useState<string[]>([]);
|
||||||
|
const [tagKeyOptions, setTagKeyOptions] = useState<TagKeyOptionItem[]>([]);
|
||||||
|
|
||||||
const [serviceList, setServiceList] = useState<string[]>([]);
|
useEffect(() => {
|
||||||
const [operationList, setOperationsList] = useState<string[]>([]);
|
metricsAPI.get<string[]>("services/list").then((response) => {
|
||||||
const [tagKeyOptions, setTagKeyOptions] = useState<TagKeyOptionItem[]>([]);
|
setServiceList(response.data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let request_string =
|
||||||
|
"service=" +
|
||||||
|
props.traceFilters.service +
|
||||||
|
"&operation=" +
|
||||||
|
props.traceFilters.operation +
|
||||||
|
"&maxDuration=" +
|
||||||
|
props.traceFilters.latency?.max +
|
||||||
|
"&minDuration=" +
|
||||||
|
props.traceFilters.latency?.min;
|
||||||
|
if (props.traceFilters.tags)
|
||||||
|
request_string =
|
||||||
|
request_string +
|
||||||
|
"&tags=" +
|
||||||
|
encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
||||||
|
|
||||||
useEffect( () => {
|
props.fetchTraces(props.globalTime, request_string);
|
||||||
metricsAPI.get<string[]>('services/list').then(response => {
|
}, [props.traceFilters, props.globalTime]);
|
||||||
setServiceList( response.data );
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect(() => {
|
||||||
let request_string='service='+props.traceFilters.service+
|
let latencyButtonText = "Latency";
|
||||||
'&operation='+props.traceFilters.operation+
|
if (
|
||||||
'&maxDuration='+props.traceFilters.latency?.max+
|
props.traceFilters.latency?.min === "" &&
|
||||||
'&minDuration='+props.traceFilters.latency?.min
|
props.traceFilters.latency?.max !== ""
|
||||||
if(props.traceFilters.tags)
|
)
|
||||||
request_string=request_string+'&tags='+encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
latencyButtonText =
|
||||||
|
"Latency<" +
|
||||||
|
(parseInt(props.traceFilters.latency?.max) / 1000000).toString() +
|
||||||
|
"ms";
|
||||||
|
else if (
|
||||||
|
props.traceFilters.latency?.min !== "" &&
|
||||||
|
props.traceFilters.latency?.max === ""
|
||||||
|
)
|
||||||
|
latencyButtonText =
|
||||||
|
"Latency>" +
|
||||||
|
(parseInt(props.traceFilters.latency?.min) / 1000000).toString() +
|
||||||
|
"ms";
|
||||||
|
else if (
|
||||||
|
props.traceFilters.latency !== undefined &&
|
||||||
|
props.traceFilters.latency?.min !== "" &&
|
||||||
|
props.traceFilters.latency?.max !== ""
|
||||||
|
)
|
||||||
|
latencyButtonText =
|
||||||
|
(parseInt(props.traceFilters.latency.min) / 1000000).toString() +
|
||||||
|
"ms <Latency<" +
|
||||||
|
(parseInt(props.traceFilters.latency.max) / 1000000).toString() +
|
||||||
|
"ms";
|
||||||
|
|
||||||
props.fetchTraces(props.globalTime, request_string)
|
form_basefilter.setFieldsValue({ latency: latencyButtonText });
|
||||||
}, [props.traceFilters,props.globalTime]);
|
}, [props.traceFilters.latency]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form_basefilter.setFieldsValue({ service: props.traceFilters.service });
|
||||||
|
}, [props.traceFilters.service]);
|
||||||
|
|
||||||
useEffect ( () => {
|
useEffect(() => {
|
||||||
|
form_basefilter.setFieldsValue({ operation: props.traceFilters.operation });
|
||||||
|
}, [props.traceFilters.operation]);
|
||||||
|
|
||||||
let latencyButtonText = 'Latency';
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
if (props.traceFilters.latency?.min === '' && props.traceFilters.latency?.max !== '')
|
const [loading] = useState(false);
|
||||||
latencyButtonText = 'Latency<'+(parseInt(props.traceFilters.latency?.max)/1000000).toString()+'ms';
|
|
||||||
else if (props.traceFilters.latency?.min !== '' && props.traceFilters.latency?.max === '')
|
|
||||||
latencyButtonText = 'Latency>'+(parseInt(props.traceFilters.latency?.min)/1000000).toString()+'ms';
|
|
||||||
else if ( props.traceFilters.latency !== undefined && props.traceFilters.latency?.min !== '' && props.traceFilters.latency?.max !== '')
|
|
||||||
latencyButtonText = (parseInt(props.traceFilters.latency.min)/1000000).toString()+'ms <Latency<'+(parseInt(props.traceFilters.latency.max)/1000000).toString()+'ms';
|
|
||||||
|
|
||||||
|
const [tagKeyValueApplied, setTagKeyValueApplied] = useState([""]);
|
||||||
form_basefilter.setFieldsValue({latency:latencyButtonText ,})
|
const [latencyFilterValues, setLatencyFilterValues] = useState({
|
||||||
|
min: "",
|
||||||
|
max: "",
|
||||||
|
});
|
||||||
|
|
||||||
}, [props.traceFilters.latency])
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
useEffect ( () => {
|
const [form_basefilter] = Form.useForm();
|
||||||
|
|
||||||
form_basefilter.setFieldsValue({service: props.traceFilters.service,})
|
|
||||||
|
|
||||||
}, [props.traceFilters.service])
|
function handleChange(value: string) {
|
||||||
|
console.log(value);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect ( () => {
|
function handleChangeOperation(value: string) {
|
||||||
|
props.updateTraceFilters({ ...props.traceFilters, operation: value });
|
||||||
form_basefilter.setFieldsValue({operation: props.traceFilters.operation,})
|
}
|
||||||
|
|
||||||
}, [props.traceFilters.operation])
|
function handleChangeService(value: string) {
|
||||||
|
let service_request = "/service/" + value + "/operations";
|
||||||
|
metricsAPI.get<string[]>(service_request).then((response) => {
|
||||||
|
// form_basefilter.resetFields(['operation',])
|
||||||
|
setOperationsList(response.data);
|
||||||
|
});
|
||||||
|
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
let tagkeyoptions_request = "tags?service=" + value;
|
||||||
const [loading] = useState(false);
|
metricsAPI.get<TagKeyOptionItem[]>(tagkeyoptions_request).then((response) => {
|
||||||
|
setTagKeyOptions(response.data);
|
||||||
|
});
|
||||||
|
|
||||||
const [tagKeyValueApplied, setTagKeyValueApplied]=useState(['']);
|
props.updateTraceFilters({ ...props.traceFilters, service: value });
|
||||||
const [latencyFilterValues, setLatencyFilterValues]=useState({min:'',max:''})
|
}
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const [form_basefilter] = Form.useForm();
|
const onLatencyButtonClick = () => {
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
function handleChange(value:string) {
|
const onLatencyModalApply = (values: Store) => {
|
||||||
console.log(value);
|
setModalVisible(false);
|
||||||
}
|
props.updateTraceFilters({
|
||||||
|
...props.traceFilters,
|
||||||
|
latency: {
|
||||||
|
min: values.min ? (parseInt(values.min) * 1000000).toString() : "",
|
||||||
|
max: values.max ? (parseInt(values.max) * 1000000).toString() : "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function handleChangeOperation(value:string) {
|
const onTagFormSubmit = (values: any) => {
|
||||||
props.updateTraceFilters({...props.traceFilters,operation:value})
|
let request_tags =
|
||||||
}
|
"service=frontend&tags=" +
|
||||||
|
encodeURIComponent(
|
||||||
|
JSON.stringify([
|
||||||
|
{
|
||||||
|
key: values.tag_key,
|
||||||
|
value: values.tag_value,
|
||||||
|
operator: values.operator,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
function handleChangeService(value:string) {
|
if (props.traceFilters.tags) {
|
||||||
let service_request='/service/'+value+'/operations';
|
// If there are existing tag filters present
|
||||||
metricsAPI.get<string[]>(service_request).then(response => {
|
props.updateTraceFilters({
|
||||||
// form_basefilter.resetFields(['operation',])
|
service: props.traceFilters.service,
|
||||||
setOperationsList( response.data );
|
operation: props.traceFilters.operation,
|
||||||
});
|
latency: props.traceFilters.latency,
|
||||||
|
tags: [
|
||||||
|
...props.traceFilters.tags,
|
||||||
|
{
|
||||||
|
key: values.tag_key,
|
||||||
|
value: values.tag_value,
|
||||||
|
operator: values.operator,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
props.updateTraceFilters({
|
||||||
|
service: props.traceFilters.service,
|
||||||
|
operation: props.traceFilters.operation,
|
||||||
|
latency: props.traceFilters.latency,
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
key: values.tag_key,
|
||||||
|
value: values.tag_value,
|
||||||
|
operator: values.operator,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let tagkeyoptions_request='tags?service='+value;
|
form.resetFields();
|
||||||
metricsAPI.get<TagKeyOptionItem[]>(tagkeyoptions_request).then(response => {
|
};
|
||||||
setTagKeyOptions( response.data );
|
|
||||||
});
|
|
||||||
|
|
||||||
props.updateTraceFilters({...props.traceFilters,service:value})
|
const onTagClose = (value: string) => {
|
||||||
|
setTagKeyValueApplied(tagKeyValueApplied.filter((e) => e !== value));
|
||||||
|
};
|
||||||
|
|
||||||
}
|
// For autocomplete
|
||||||
|
//Setting value when autocomplete field is changed
|
||||||
|
const onChangeTagKey = (data: string) => {
|
||||||
|
form.setFieldsValue({ tag_key: data });
|
||||||
|
};
|
||||||
|
|
||||||
const onLatencyButtonClick = () => {
|
const dataSource = ["status:200"];
|
||||||
setModalVisible(true);
|
const children = [];
|
||||||
}
|
for (let i = 0; i < dataSource.length; i++) {
|
||||||
|
children.push(
|
||||||
|
<Option value={dataSource[i]} key={dataSource[i]}>
|
||||||
const onLatencyModalApply = (values: Store) => {
|
{dataSource[i]}
|
||||||
setModalVisible(false);
|
</Option>,
|
||||||
props.updateTraceFilters({...props.traceFilters,latency:{min:values.min?(parseInt(values.min)*1000000).toString():"", max:values.max?(parseInt(values.max)*1000000).toString():""}})
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
// PNOTE - Remove any
|
||||||
|
const handleApplyFilterForm = (values: any) => {
|
||||||
|
let request_params: string = "";
|
||||||
|
if (
|
||||||
|
typeof values.service !== undefined &&
|
||||||
|
typeof values.operation !== undefined
|
||||||
|
) {
|
||||||
|
request_params =
|
||||||
|
"service=" + values.service + "&operation=" + values.operation;
|
||||||
|
} else if (
|
||||||
|
typeof values.service === undefined &&
|
||||||
|
typeof values.operation !== undefined
|
||||||
|
) {
|
||||||
|
request_params = "operation=" + values.operation;
|
||||||
|
} else if (
|
||||||
|
typeof values.service !== undefined &&
|
||||||
|
typeof values.operation === undefined
|
||||||
|
) {
|
||||||
|
request_params = "service=" + values.service;
|
||||||
|
}
|
||||||
|
|
||||||
const onTagFormSubmit = (values:any) => {
|
request_params =
|
||||||
|
request_params +
|
||||||
let request_tags= 'service=frontend&tags='+encodeURIComponent(JSON.stringify([{"key":values.tag_key,"value":values.tag_value,"operator":values.operator}]))
|
"&minDuration=" +
|
||||||
|
latencyFilterValues.min +
|
||||||
|
"&maxDuration=" +
|
||||||
|
latencyFilterValues.max;
|
||||||
|
|
||||||
|
setTagKeyValueApplied((tagKeyValueApplied) => [
|
||||||
|
...tagKeyValueApplied,
|
||||||
|
"service eq" + values.service,
|
||||||
|
"operation eq " + values.operation,
|
||||||
|
"maxduration eq " + (parseInt(latencyFilterValues.max) / 1000000).toString(),
|
||||||
|
"minduration eq " + (parseInt(latencyFilterValues.min) / 1000000).toString(),
|
||||||
|
]);
|
||||||
|
props.updateTraceFilters({
|
||||||
|
service: values.service,
|
||||||
|
operation: values.operation,
|
||||||
|
latency: latencyFilterValues,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (props.traceFilters.tags){ // If there are existing tag filters present
|
return (
|
||||||
props.updateTraceFilters(
|
<div>
|
||||||
{
|
<div>Filter Traces</div>
|
||||||
service:props.traceFilters.service,
|
{/* <div>{JSON.stringify(props.traceFilters)}</div> */}
|
||||||
operation:props.traceFilters.operation,
|
|
||||||
latency:props.traceFilters.latency,
|
|
||||||
tags:[...props.traceFilters.tags, {'key':values.tag_key,'value':values.tag_value,'operator':values.operator}]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
props.updateTraceFilters(
|
|
||||||
{
|
|
||||||
service:props.traceFilters.service,
|
|
||||||
operation:props.traceFilters.operation,
|
|
||||||
latency:props.traceFilters.latency,
|
|
||||||
tags:[ {'key':values.tag_key,'value':values.tag_value,'operator':values.operator}]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
form.resetFields();
|
<Form
|
||||||
}
|
form={form_basefilter}
|
||||||
|
layout="inline"
|
||||||
|
onFinish={handleApplyFilterForm}
|
||||||
|
initialValues={{ service: "", operation: "", latency: "Latency" }}
|
||||||
|
style={{ marginTop: 10, marginBottom: 10 }}
|
||||||
|
>
|
||||||
|
<FormItem rules={[{ required: true }]} name="service">
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
style={{ width: 180 }}
|
||||||
|
onChange={handleChangeService}
|
||||||
|
placeholder="Select Service"
|
||||||
|
allowClear
|
||||||
|
>
|
||||||
|
{serviceList.map((s) => (
|
||||||
|
<Option value={s}>{s}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
const onTagClose = (value:string) => {
|
<FormItem name="operation">
|
||||||
setTagKeyValueApplied(tagKeyValueApplied.filter( e => (e !== value)));
|
<Select
|
||||||
|
showSearch
|
||||||
|
style={{ width: 180 }}
|
||||||
|
onChange={handleChangeOperation}
|
||||||
|
placeholder="Select Operation"
|
||||||
|
allowClear
|
||||||
|
>
|
||||||
|
{operationList.map((item) => (
|
||||||
|
<Option value={item}>{item}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
}
|
<FormItem name="latency">
|
||||||
|
<Input
|
||||||
|
style={{ width: 200 }}
|
||||||
|
type="button"
|
||||||
|
onClick={onLatencyButtonClick}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
// For autocomplete
|
{/* <FormItem>
|
||||||
//Setting value when autocomplete field is changed
|
|
||||||
const onChangeTagKey = (data: string) => {
|
|
||||||
form.setFieldsValue({ tag_key: data });
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataSource = ['status:200'];
|
|
||||||
const children = [];
|
|
||||||
for (let i = 0; i < dataSource.length; i++) {
|
|
||||||
children.push(<Option value={dataSource[i]} key={dataSource[i]}>{dataSource[i]}</Option>);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// PNOTE - Remove any
|
|
||||||
const handleApplyFilterForm = (values:any) => {
|
|
||||||
|
|
||||||
let request_params: string ='';
|
|
||||||
if (typeof values.service !== undefined && typeof(values.operation) !== undefined)
|
|
||||||
{
|
|
||||||
request_params = 'service='+values.service+'&operation='+values.operation;
|
|
||||||
}
|
|
||||||
else if (typeof values.service === undefined && typeof values.operation !== undefined)
|
|
||||||
{
|
|
||||||
request_params = 'operation='+values.operation;
|
|
||||||
}
|
|
||||||
else if (typeof values.service !== undefined && typeof values.operation === undefined)
|
|
||||||
{
|
|
||||||
request_params = 'service='+values.service;
|
|
||||||
}
|
|
||||||
|
|
||||||
request_params=request_params+'&minDuration='+latencyFilterValues.min+'&maxDuration='+latencyFilterValues.max;
|
|
||||||
|
|
||||||
|
|
||||||
setTagKeyValueApplied(tagKeyValueApplied => [...tagKeyValueApplied, 'service eq'+values.service, 'operation eq '+values.operation, 'maxduration eq '+ (parseInt(latencyFilterValues.max)/1000000).toString(), 'minduration eq '+(parseInt(latencyFilterValues.min)/1000000).toString()]);
|
|
||||||
props.updateTraceFilters({'service':values.service,'operation':values.operation,'latency':latencyFilterValues})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>Filter Traces</div>
|
|
||||||
{/* <div>{JSON.stringify(props.traceFilters)}</div> */}
|
|
||||||
|
|
||||||
<Form form={form_basefilter} layout='inline' onFinish={handleApplyFilterForm} initialValues={{ service:'', operation:'',latency:'Latency',}} style={{marginTop: 10, marginBottom:10}}>
|
|
||||||
<FormItem rules={[{ required: true }]} name='service'>
|
|
||||||
<Select showSearch style={{ width: 180 }} onChange={handleChangeService} placeholder='Select Service' allowClear>
|
|
||||||
{serviceList.map( s => <Option value={s}>{s}</Option>)}
|
|
||||||
</Select>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<FormItem name='operation'>
|
|
||||||
<Select showSearch style={{ width: 180 }} onChange={handleChangeOperation} placeholder='Select Operation' allowClear>
|
|
||||||
{operationList.map( item => <Option value={item}>{item}</Option>)}
|
|
||||||
</Select>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<FormItem name='latency'>
|
|
||||||
<Input style={{ width: 200 }} type='button' onClick={onLatencyButtonClick}/>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
{/* <FormItem>
|
|
||||||
<Button type="primary" htmlType="submit">Apply Filters</Button>
|
<Button type="primary" htmlType="submit">Apply Filters</Button>
|
||||||
</FormItem> */}
|
</FormItem> */}
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<FilterStateDisplay />
|
|
||||||
|
|
||||||
|
<FilterStateDisplay />
|
||||||
|
|
||||||
{/* // What will be the empty state of card when there is no Tag , it should show something */}
|
{/* // What will be the empty state of card when there is no Tag , it should show something */}
|
||||||
|
|
||||||
<InfoWrapper>Select Service to get Tag suggestions </InfoWrapper>
|
|
||||||
|
|
||||||
<Form form={form} layout='inline' onFinish={onTagFormSubmit} initialValues={{operator:'equals'}} style={{marginTop: 10, marginBottom:10}}>
|
|
||||||
|
|
||||||
<FormItem rules={[{ required: true }]} name='tag_key'>
|
<InfoWrapper>Select Service to get Tag suggestions </InfoWrapper>
|
||||||
|
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="inline"
|
||||||
|
onFinish={onTagFormSubmit}
|
||||||
|
initialValues={{ operator: "equals" }}
|
||||||
|
style={{ marginTop: 10, marginBottom: 10 }}
|
||||||
|
>
|
||||||
|
<FormItem rules={[{ required: true }]} name="tag_key">
|
||||||
|
<AutoComplete
|
||||||
|
options={tagKeyOptions.map((s) => {
|
||||||
|
return { value: s.tagKeys };
|
||||||
|
})}
|
||||||
|
style={{ width: 200, textAlign: "center" }}
|
||||||
|
// onSelect={onSelect}
|
||||||
|
// onSearch={onSearch}
|
||||||
|
onChange={onChangeTagKey}
|
||||||
|
filterOption={(inputValue, option) =>
|
||||||
|
option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||||
|
}
|
||||||
|
placeholder="Tag Key"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
<AutoComplete
|
<FormItem name="operator">
|
||||||
options={tagKeyOptions.map((s) => { return ({'value' : s.tagKeys}) })}
|
<Select style={{ width: 120, textAlign: "center" }}>
|
||||||
style={{ width: 200, textAlign: 'center' }}
|
<Option value="equals">EQUAL</Option>
|
||||||
// onSelect={onSelect}
|
<Option value="contains">CONTAINS</Option>
|
||||||
// onSearch={onSearch}
|
</Select>
|
||||||
onChange={onChangeTagKey}
|
</FormItem>
|
||||||
filterOption={(inputValue, option) =>
|
|
||||||
option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
|
||||||
}
|
|
||||||
placeholder="Tag Key"
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<FormItem name='operator'>
|
<FormItem rules={[{ required: true }]} name="tag_value">
|
||||||
<Select style={{ width: 120, textAlign: 'center' }}>
|
<Input
|
||||||
<Option value="equals">EQUAL</Option>
|
style={{ width: 160, textAlign: "center" }}
|
||||||
<Option value="contains">CONTAINS</Option>
|
placeholder="Tag Value"
|
||||||
</Select>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem rules={[{ required: true }]} name='tag_value'>
|
<FormItem>
|
||||||
<Input style={{ width: 160, textAlign: 'center',}} placeholder="Tag Value" />
|
<Button type="primary" htmlType="submit">
|
||||||
</FormItem>
|
{" "}
|
||||||
|
Apply Tag Filter{" "}
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
|
||||||
<FormItem>
|
<LatencyModalForm
|
||||||
<Button type="primary" htmlType="submit"> Apply Tag Filter </Button>
|
visible={modalVisible}
|
||||||
</FormItem>
|
onCreate={onLatencyModalApply}
|
||||||
|
onCancel={() => {
|
||||||
|
setModalVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
</Form>
|
const mapStateToProps = (
|
||||||
|
state: StoreState,
|
||||||
<LatencyModalForm
|
): { traceFilters: TraceFilters; globalTime: GlobalTime } => {
|
||||||
visible={modalVisible}
|
return { traceFilters: state.traceFilters, globalTime: state.globalTime };
|
||||||
onCreate={onLatencyModalApply}
|
|
||||||
onCancel={() => {
|
|
||||||
setModalVisible(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters, globalTime: GlobalTime } => {
|
|
||||||
return { traceFilters: state.traceFilters, globalTime: state.globalTime };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TraceFilter = connect(mapStateToProps, {
|
export const TraceFilter = connect(mapStateToProps, {
|
||||||
updateTraceFilters: updateTraceFilters,
|
updateTraceFilters: updateTraceFilters,
|
||||||
fetchTraces: fetchTraces,
|
fetchTraces: fetchTraces,
|
||||||
})(_TraceFilter);
|
})(_TraceFilter);
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
.d3-tip {
|
.d3-tip {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
background: rgba(0, 0, 0, 0.8);
|
background: rgba(0, 0, 0, 0.8);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-size: 6px;
|
font-size: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Creates a small triangle extender for the tooltip */
|
/* Creates a small triangle extender for the tooltip */
|
||||||
.d3-tip:after {
|
.d3-tip:after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: inline;
|
display: inline;
|
||||||
font-size: 6px;
|
font-size: 6px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: rgba(0, 0, 0, 0.8);
|
color: rgba(0, 0, 0, 0.8);
|
||||||
content: "\25BC";
|
content: "\25BC";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style northward tooltips differently */
|
/* Style northward tooltips differently */
|
||||||
.d3-tip.n:after {
|
.d3-tip.n:after {
|
||||||
margin: -1px 0 0 0;
|
margin: -1px 0 0 0;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SVG element */
|
/* SVG element */
|
||||||
/* Way to add borders in SVG - https://stackoverflow.com/questions/18330344/how-to-add-border-outline-stroke-to-svg-elements-in-css */
|
/* Way to add borders in SVG - https://stackoverflow.com/questions/18330344/how-to-add-border-outline-stroke-to-svg-elements-in-css */
|
||||||
.frame {
|
.frame {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: rgba(255, 255, 255, 0.25);
|
stroke: rgba(255, 255, 255, 0.25);
|
||||||
stroke-width: 1;
|
stroke-width: 1;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
}
|
}
|
||||||
/* Transparency simulates sub pixel border https://stackoverflow.com/questions/13891177/css-border-less-than-1px */
|
/* Transparency simulates sub pixel border https://stackoverflow.com/questions/13891177/css-border-less-than-1px */
|
||||||
|
|
||||||
.d3-flame-graph-label:hover {
|
.d3-flame-graph-label:hover {
|
||||||
border: 1px dotted;
|
border: 1px dotted;
|
||||||
border-color: rgba(255, 255, 255, 0.75);
|
border-color: rgba(255, 255, 255, 0.75);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
.d3-flame-graph-label:hover {
|
.d3-flame-graph-label:hover {
|
||||||
|
@ -1,105 +1,102 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { flamegraph } from 'd3-flame-graph'
|
import { flamegraph } from "d3-flame-graph";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import { Card, Button, Row, Col, Space } from 'antd';
|
import { Card, Button, Row, Col, Space } from "antd";
|
||||||
import * as d3 from 'd3';
|
import * as d3 from "d3";
|
||||||
import * as d3Tip from 'd3-tip';
|
import * as d3Tip from "d3-tip";
|
||||||
|
|
||||||
//import * as d3Tip from 'd3-tip';
|
//import * as d3Tip from 'd3-tip';
|
||||||
// PNOTE - uninstall @types/d3-tip. issues with importing d3-tip https://github.com/Caged/d3-tip/issues/181
|
// PNOTE - uninstall @types/d3-tip. issues with importing d3-tip https://github.com/Caged/d3-tip/issues/181
|
||||||
|
|
||||||
import './TraceGraph.css'
|
import "./TraceGraph.css";
|
||||||
import { spanToTreeUtil } from '../../utils/spanToTree'
|
import { spanToTreeUtil } from "../../utils/spanToTree";
|
||||||
import { fetchTraceItem , spansWSameTraceIDResponse } from '../../actions';
|
import { fetchTraceItem, spansWSameTraceIDResponse } from "../../actions";
|
||||||
import { StoreState } from '../../reducers'
|
import { StoreState } from "../../reducers";
|
||||||
import { TraceGraphColumn } from './TraceGraphColumn'
|
import { TraceGraphColumn } from "./TraceGraphColumn";
|
||||||
import SelectedSpanDetails from './SelectedSpanDetails'
|
import SelectedSpanDetails from "./SelectedSpanDetails";
|
||||||
|
|
||||||
|
|
||||||
interface TraceGraphProps {
|
interface TraceGraphProps {
|
||||||
|
traceItem: spansWSameTraceIDResponse;
|
||||||
traceItem: spansWSameTraceIDResponse ,
|
fetchTraceItem: Function;
|
||||||
fetchTraceItem: Function,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const _TraceGraph = (props: TraceGraphProps) => {
|
const _TraceGraph = (props: TraceGraphProps) => {
|
||||||
|
const params = useParams<{ id?: string }>();
|
||||||
|
const [clickedSpanTags, setClickedSpanTags] = useState([]);
|
||||||
|
const [resetZoom, setResetZoom] = useState(false);
|
||||||
|
|
||||||
const params = useParams<{ id?: string; }>();
|
useEffect(() => {
|
||||||
const [clickedSpanTags,setClickedSpanTags]=useState([])
|
//sets span width based on value - which is mapped to duration
|
||||||
const [resetZoom,setResetZoom]=useState(false)
|
props.fetchTraceItem(params.id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect(() => {
|
||||||
//sets span width based on value - which is mapped to duration
|
if (props.traceItem || resetZoom) {
|
||||||
props.fetchTraceItem(params.id);
|
const tree = spanToTreeUtil(props.traceItem[0].events);
|
||||||
}, []);
|
// This is causing element to change ref. Can use both useRef or this approach.
|
||||||
|
d3.select("#chart").datum(tree).call(chart);
|
||||||
|
setResetZoom(false);
|
||||||
|
}
|
||||||
|
}, [props.traceItem, resetZoom]);
|
||||||
|
// if this monitoring of props.traceItem.data is removed then zoom on click doesn't work
|
||||||
|
// Doesn't work if only do initial check, works if monitor an element - as it may get updated in sometime
|
||||||
|
|
||||||
useEffect( () => {
|
const tip = d3Tip
|
||||||
if (props.traceItem || resetZoom)
|
.default()
|
||||||
{
|
.attr("class", "d3-tip")
|
||||||
const tree = spanToTreeUtil(props.traceItem[0].events);
|
.html(function (d: any) {
|
||||||
// This is causing element to change ref. Can use both useRef or this approach.
|
return d.data.name + "<br>duration: " + d.data.value;
|
||||||
d3.select("#chart").datum(tree).call(chart)
|
});
|
||||||
setResetZoom(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
},[props.traceItem,resetZoom]);
|
const onClick = (z: any) => {
|
||||||
// if this monitoring of props.traceItem.data is removed then zoom on click doesn't work
|
setClickedSpanTags(z.data.tags);
|
||||||
// Doesn't work if only do initial check, works if monitor an element - as it may get updated in sometime
|
console.log(`Clicked on ${z.data.name}, id: "${z.id}"`);
|
||||||
|
};
|
||||||
|
|
||||||
const tip = d3Tip.default().attr('class', 'd3-tip').html(function(d:any) { return d.data.name+'<br>duration: '+d.data.value});
|
const chart = flamegraph()
|
||||||
|
.width(640)
|
||||||
|
.cellHeight(18)
|
||||||
|
.transitionDuration(500)
|
||||||
|
.minFrameSize(5)
|
||||||
|
.sort(true)
|
||||||
|
.inverted(true)
|
||||||
|
.tooltip(tip)
|
||||||
|
.elided(false)
|
||||||
|
.onClick(onClick)
|
||||||
|
// .title("Trace Flame graph")
|
||||||
|
.differential(false)
|
||||||
|
.selfValue(true); //sets span width based on value - which is mapped to duration
|
||||||
|
|
||||||
const onClick = (z:any) => {
|
return (
|
||||||
setClickedSpanTags(z.data.tags);
|
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
|
||||||
console.log(`Clicked on ${z.data.name}, id: "${z.id}"`);
|
<Col md={8} sm={24}>
|
||||||
}
|
<TraceGraphColumn />
|
||||||
|
</Col>
|
||||||
|
<Col md={16} sm={24}>
|
||||||
|
{/* <Card style={{ width: 640 }}> */}
|
||||||
|
<Space direction="vertical" size="middle">
|
||||||
|
<Card bodyStyle={{ padding: 80 }} style={{ height: 320 }}>
|
||||||
|
<div>Trace Graph component ID is {params.id} </div>
|
||||||
|
<Button type="primary" onClick={setResetZoom.bind(this, true)}>
|
||||||
|
Reset Zoom
|
||||||
|
</Button>
|
||||||
|
<div id="chart" style={{ fontSize: 12 }}></div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
const chart = flamegraph()
|
<SelectedSpanDetails clickedSpanTags={clickedSpanTags} />
|
||||||
.width(640)
|
</Space>
|
||||||
.cellHeight(18)
|
</Col>
|
||||||
.transitionDuration(500)
|
</Row>
|
||||||
.minFrameSize(5)
|
);
|
||||||
.sort(true)
|
|
||||||
.inverted(true)
|
|
||||||
.tooltip(tip)
|
|
||||||
.elided(false)
|
|
||||||
.onClick(onClick)
|
|
||||||
// .title("Trace Flame graph")
|
|
||||||
.differential(false)
|
|
||||||
.selfValue(true); //sets span width based on value - which is mapped to duration
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
|
|
||||||
<Col md={8} sm={24} >
|
|
||||||
<TraceGraphColumn />
|
|
||||||
</Col>
|
|
||||||
<Col md={16} sm={24} >
|
|
||||||
{/* <Card style={{ width: 640 }}> */}
|
|
||||||
<Space direction="vertical" size='middle' >
|
|
||||||
|
|
||||||
<Card bodyStyle={{padding: 80, }} style={{ height: 320, }}>
|
|
||||||
<div>Trace Graph component ID is {params.id} </div>
|
|
||||||
<Button type="primary" onClick={setResetZoom.bind(this,true)}>Reset Zoom</Button>
|
|
||||||
<div id="chart" style={{ fontSize: 12 }}></div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<SelectedSpanDetails clickedSpanTags={clickedSpanTags}/>
|
|
||||||
|
|
||||||
</Space>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { traceItem: spansWSameTraceIDResponse } => {
|
|
||||||
return { traceItem: state.traceItem };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (
|
||||||
|
state: StoreState,
|
||||||
|
): { traceItem: spansWSameTraceIDResponse } => {
|
||||||
|
return { traceItem: state.traceItem };
|
||||||
|
};
|
||||||
|
|
||||||
export const TraceGraph = connect(mapStateToProps, {
|
export const TraceGraph = connect(mapStateToProps, {
|
||||||
fetchTraceItem: fetchTraceItem,
|
fetchTraceItem: fetchTraceItem,
|
||||||
})(_TraceGraph);
|
})(_TraceGraph);
|
||||||
|
@ -1,73 +1,77 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import { Table } from 'antd'
|
import { Table } from "antd";
|
||||||
|
|
||||||
import { traceResponseNew, pushDStree } from '../../actions';
|
|
||||||
import { StoreState } from '../../reducers'
|
|
||||||
|
|
||||||
|
import { traceResponseNew, pushDStree } from "../../actions";
|
||||||
|
import { StoreState } from "../../reducers";
|
||||||
|
|
||||||
interface TraceGraphColumnProps {
|
interface TraceGraphColumnProps {
|
||||||
traces: traceResponseNew,
|
traces: traceResponseNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TableDataSourceItem {
|
interface TableDataSourceItem {
|
||||||
key: string;
|
key: string;
|
||||||
operationName: string;
|
operationName: string;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const _TraceGraphColumn = (props: TraceGraphColumnProps) => {
|
|
||||||
|
|
||||||
const columns: any = [
|
|
||||||
{
|
|
||||||
title: 'Start Time (UTC Time)',
|
|
||||||
dataIndex: 'startTime',
|
|
||||||
key: 'startTime',
|
|
||||||
sorter: (a:any, b:any) => a.startTime - b.startTime,
|
|
||||||
sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (new Date(Math.round(value/1000))).toUTCString()
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Duration (in ms)',
|
|
||||||
dataIndex: 'duration',
|
|
||||||
key: 'duration',
|
|
||||||
sorter: (a:any, b:any) => a.duration - b.duration,
|
|
||||||
sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value/1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Operation',
|
|
||||||
dataIndex: 'operationName',
|
|
||||||
key: 'operationName',
|
|
||||||
},
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
let dataSource :TableDataSourceItem[] = [];
|
|
||||||
|
|
||||||
if (props.traces[0].events.length > 0) {
|
|
||||||
|
|
||||||
props.traces[0].events.map((item: (number|string|string[]|pushDStree[])[], index ) => {
|
|
||||||
if (typeof item[0] === 'number' && typeof item[4] === 'string' && typeof item[6] === 'string' && typeof item[1] === 'string' && typeof item[2] === 'string' )
|
|
||||||
dataSource.push({startTime: item[0], operationName: item[4] , duration:parseInt(item[6]), key:index.toString()});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Table dataSource={dataSource} columns={columns} size="middle"/>;
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
|
const _TraceGraphColumn = (props: TraceGraphColumnProps) => {
|
||||||
return { traces : state.traces };
|
const columns: any = [
|
||||||
};
|
{
|
||||||
|
title: "Start Time (UTC Time)",
|
||||||
|
dataIndex: "startTime",
|
||||||
|
key: "startTime",
|
||||||
|
sorter: (a: any, b: any) => a.startTime - b.startTime,
|
||||||
|
sortDirections: ["descend", "ascend"],
|
||||||
|
render: (value: number) => new Date(Math.round(value / 1000)).toUTCString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Duration (in ms)",
|
||||||
|
dataIndex: "duration",
|
||||||
|
key: "duration",
|
||||||
|
sorter: (a: any, b: any) => a.duration - b.duration,
|
||||||
|
sortDirections: ["descend", "ascend"],
|
||||||
|
render: (value: number) => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Operation",
|
||||||
|
dataIndex: "operationName",
|
||||||
|
key: "operationName",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let dataSource: TableDataSourceItem[] = [];
|
||||||
export const TraceGraphColumn = connect(mapStateToProps)(_TraceGraphColumn);
|
|
||||||
|
if (props.traces[0].events.length > 0) {
|
||||||
|
props.traces[0].events.map(
|
||||||
|
(item: (number | string | string[] | pushDStree[])[], index) => {
|
||||||
|
if (
|
||||||
|
typeof item[0] === "number" &&
|
||||||
|
typeof item[4] === "string" &&
|
||||||
|
typeof item[6] === "string" &&
|
||||||
|
typeof item[1] === "string" &&
|
||||||
|
typeof item[2] === "string"
|
||||||
|
)
|
||||||
|
dataSource.push({
|
||||||
|
startTime: item[0],
|
||||||
|
operationName: item[4],
|
||||||
|
duration: parseInt(item[6]),
|
||||||
|
key: index.toString(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table dataSource={dataSource} columns={columns} size="middle" />;
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
|
||||||
|
return { traces: state.traces };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TraceGraphColumn = connect(mapStateToProps)(_TraceGraphColumn);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export { TraceGraph as default } from './TraceGraph';
|
export { TraceGraph as default } from "./TraceGraph";
|
||||||
|
|
||||||
// PNOTE
|
// PNOTE
|
||||||
// Because react.lazy doesn't work on named components
|
// Because react.lazy doesn't work on named components
|
||||||
// https://reactjs.org/docs/code-splitting.html#:~:text=Named%20Exports,t%20pull%20in%20unused%20components
|
// https://reactjs.org/docs/code-splitting.html#:~:text=Named%20Exports,t%20pull%20in%20unused%20components
|
||||||
|
@ -1,116 +1,127 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from "react-router-dom";
|
||||||
import { Table } from 'antd'
|
import { Table } from "antd";
|
||||||
|
|
||||||
import { traceResponseNew, fetchTraces, pushDStree } from '../../actions';
|
import { traceResponseNew, fetchTraces, pushDStree } from "../../actions";
|
||||||
import { StoreState } from '../../reducers'
|
import { StoreState } from "../../reducers";
|
||||||
|
|
||||||
interface TraceListProps {
|
interface TraceListProps {
|
||||||
traces: traceResponseNew,
|
traces: traceResponseNew;
|
||||||
fetchTraces: Function,
|
fetchTraces: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TableDataSourceItem {
|
interface TableDataSourceItem {
|
||||||
key: string;
|
key: string;
|
||||||
spanid: string;
|
spanid: string;
|
||||||
traceid: string;
|
traceid: string;
|
||||||
operationName: string;
|
operationName: string;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const _TraceList = (props: TraceListProps) => {
|
const _TraceList = (props: TraceListProps) => {
|
||||||
|
// PNOTE (TO DO) - Currently this use of useEffect gives warning. May need to memoise fetchtraces - https://stackoverflow.com/questions/55840294/how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook
|
||||||
// PNOTE (TO DO) - Currently this use of useEffect gives warning. May need to memoise fetchtraces - https://stackoverflow.com/questions/55840294/how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook
|
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect(() => {
|
||||||
props.fetchTraces();
|
props.fetchTraces();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// PNOTE - code snippet -
|
// PNOTE - code snippet -
|
||||||
// renderList(): JSX.Element[] {
|
// renderList(): JSX.Element[] {
|
||||||
// return this.props.todos.map((todo: Todo) => {
|
// return this.props.todos.map((todo: Todo) => {
|
||||||
// return (
|
// return (
|
||||||
// <div onClick={() => this.onTodoClick(todo.id)} key={todo.id}>
|
// <div onClick={() => this.onTodoClick(todo.id)} key={todo.id}>
|
||||||
// {todo.title}
|
// {todo.title}
|
||||||
// </div>
|
// </div>
|
||||||
// );
|
// );
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
title: 'Start Time (UTC Time)',
|
title: "Start Time (UTC Time)",
|
||||||
dataIndex: 'startTime',
|
dataIndex: "startTime",
|
||||||
key: 'startTime',
|
key: "startTime",
|
||||||
sorter: (a:any, b:any) => a.startTime - b.startTime,
|
sorter: (a: any, b: any) => a.startTime - b.startTime,
|
||||||
sortDirections: ['descend', 'ascend'],
|
sortDirections: ["descend", "ascend"],
|
||||||
render: (value: number) => (new Date(Math.round(value))).toUTCString()
|
render: (value: number) => new Date(Math.round(value)).toUTCString(),
|
||||||
|
|
||||||
// new Date() assumes input in milliseconds. Start Time stamp returned by druid api for span list is in ms
|
// new Date() assumes input in milliseconds. Start Time stamp returned by druid api for span list is in ms
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Duration (in ms)",
|
||||||
|
dataIndex: "duration",
|
||||||
|
key: "duration",
|
||||||
|
sorter: (a: any, b: any) => a.duration - b.duration,
|
||||||
|
sortDirections: ["descend", "ascend"],
|
||||||
|
render: (value: number) => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Operation",
|
||||||
|
dataIndex: "operationName",
|
||||||
|
key: "operationName",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "TraceID",
|
||||||
|
dataIndex: "traceid",
|
||||||
|
key: "traceid",
|
||||||
|
render: (text: string) => (
|
||||||
|
<NavLink to={"/traces/" + text}>{text.slice(-16)}</NavLink>
|
||||||
|
),
|
||||||
|
//only last 16 chars have traceID, druid makes it 32 by adding zeros
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
},
|
let dataSource: TableDataSourceItem[] = [];
|
||||||
{
|
|
||||||
title: 'Duration (in ms)',
|
|
||||||
dataIndex: 'duration',
|
|
||||||
key: 'duration',
|
|
||||||
sorter: (a:any, b:any) => a.duration - b.duration,
|
|
||||||
sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value/1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Operation',
|
|
||||||
dataIndex: 'operationName',
|
|
||||||
key: 'operationName',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'TraceID',
|
|
||||||
dataIndex: 'traceid',
|
|
||||||
key: 'traceid',
|
|
||||||
render: (text :string) => <NavLink to={'/traces/' + text}>{text.slice(-16)}</NavLink>,
|
|
||||||
//only last 16 chars have traceID, druid makes it 32 by adding zeros
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
const renderTraces = () => {
|
||||||
let dataSource :TableDataSourceItem[] = [];
|
if (
|
||||||
|
typeof props.traces[0] !== "undefined" &&
|
||||||
|
props.traces[0].events.length > 0
|
||||||
|
) {
|
||||||
|
//PNOTE - Template literal should be wrapped in curly braces for it to be evaluated
|
||||||
|
|
||||||
const renderTraces = () => {
|
props.traces[0].events.map(
|
||||||
|
(item: (number | string | string[] | pushDStree[])[], index) => {
|
||||||
if (typeof props.traces[0]!== 'undefined' && props.traces[0].events.length > 0) {
|
if (
|
||||||
//PNOTE - Template literal should be wrapped in curly braces for it to be evaluated
|
typeof item[0] === "number" &&
|
||||||
|
typeof item[4] === "string" &&
|
||||||
|
typeof item[6] === "string" &&
|
||||||
|
typeof item[1] === "string" &&
|
||||||
|
typeof item[2] === "string"
|
||||||
|
)
|
||||||
|
dataSource.push({
|
||||||
|
startTime: item[0],
|
||||||
|
operationName: item[4],
|
||||||
|
duration: parseInt(item[6]),
|
||||||
|
spanid: item[1],
|
||||||
|
traceid: item[2],
|
||||||
|
key: index.toString(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
props.traces[0].events.map((item: (number|string|string[]|pushDStree[])[], index ) => {
|
//antd table in typescript - https://codesandbox.io/s/react-typescript-669cv
|
||||||
if (typeof item[0] === 'number' && typeof item[4] === 'string' && typeof item[6] === 'string' && typeof item[1] === 'string' && typeof item[2] === 'string' )
|
|
||||||
dataSource.push({startTime: item[0], operationName: item[4] , duration:parseInt(item[6]), spanid:item[1], traceid:item[2], key:index.toString()});
|
|
||||||
});
|
|
||||||
|
|
||||||
//antd table in typescript - https://codesandbox.io/s/react-typescript-669cv
|
|
||||||
|
|
||||||
return <Table dataSource={dataSource} columns={columns} size="middle"/>;
|
return <Table dataSource={dataSource} columns={columns} size="middle" />;
|
||||||
} else
|
} else {
|
||||||
{
|
return <div> No spans found for given filter!</div>;
|
||||||
return <div> No spans found for given filter!</div>
|
}
|
||||||
}
|
}; // end of renderTraces
|
||||||
|
|
||||||
};// end of renderTraces
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
<div>List of traces with spanID</div>
|
|
||||||
<div>{renderTraces()}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
return (
|
||||||
|
<div>
|
||||||
|
<div>List of traces with spanID</div>
|
||||||
|
<div>{renderTraces()}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
|
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
|
||||||
return { traces : state.traces };
|
return { traces: state.traces };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TraceList = connect(mapStateToProps, {
|
export const TraceList = connect(mapStateToProps, {
|
||||||
fetchTraces: fetchTraces,
|
fetchTraces: fetchTraces,
|
||||||
})(_TraceList);
|
})(_TraceList);
|
||||||
|
@ -1,80 +1,78 @@
|
|||||||
import React, {useEffect} from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { Bar } from 'react-chartjs-2'
|
import { Bar } from "react-chartjs-2";
|
||||||
import { Card } from 'antd'
|
import { Card } from "antd";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { getUsageData, GlobalTime, usageDataItem } from '../../actions';
|
import { getUsageData, GlobalTime, usageDataItem } from "../../actions";
|
||||||
import { StoreState } from '../../reducers'
|
import { StoreState } from "../../reducers";
|
||||||
|
|
||||||
interface UsageExplorerProps {
|
interface UsageExplorerProps {
|
||||||
usageData: usageDataItem[],
|
usageData: usageDataItem[];
|
||||||
getUsageData: Function,
|
getUsageData: Function;
|
||||||
globalTime: GlobalTime,
|
globalTime: GlobalTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const _UsageExplorer = (props: UsageExplorerProps) => {
|
const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||||
|
useEffect(() => {
|
||||||
|
props.getUsageData(props.globalTime);
|
||||||
|
}, [props.globalTime]);
|
||||||
|
|
||||||
useEffect( () => {
|
const data = {
|
||||||
props.getUsageData(props.globalTime);
|
labels: props.usageData.map((s) => new Date(s.timestamp / 1000000)),
|
||||||
}, [props.globalTime]);
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Span Count",
|
||||||
|
data: props.usageData.map((s) => s.count),
|
||||||
|
backgroundColor: "rgba(255, 99, 132, 0.2)",
|
||||||
|
borderColor: "rgba(255, 99, 132, 1)",
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const data = {
|
const options = {
|
||||||
labels: props.usageData.map(s => new Date(s.timestamp/1000000)),
|
scales: {
|
||||||
datasets: [
|
yAxes: [
|
||||||
{
|
{
|
||||||
label: 'Span Count',
|
ticks: {
|
||||||
data: props.usageData.map(s => s.count),
|
beginAtZero: true,
|
||||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
fontSize: 10,
|
||||||
borderColor: 'rgba(255, 99, 132, 1)',
|
},
|
||||||
borderWidth: 2,
|
},
|
||||||
},
|
],
|
||||||
],
|
xAxes: [
|
||||||
}
|
{
|
||||||
|
type: "time",
|
||||||
const options = {
|
// distribution: 'linear', // Bar graph doesn't take lineardistribution type?
|
||||||
scales: {
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
fontSize: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
xAxes: [
|
|
||||||
{
|
|
||||||
type: 'time',
|
|
||||||
// distribution: 'linear', // Bar graph doesn't take lineardistribution type?
|
|
||||||
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
fontSize: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<React.Fragment>
|
|
||||||
{/* PNOTE - TODO - Keep it in reponsive row column tab */}
|
|
||||||
<Card style={{ width: "50%" , margin:20 }} bodyStyle={{padding:20 }}>
|
|
||||||
<Bar data={data} options={options} />
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState): { usageData: usageDataItem[], globalTime: GlobalTime } => {
|
ticks: {
|
||||||
return { usageData : state.usageDate, globalTime: state.globalTime };
|
beginAtZero: true,
|
||||||
|
fontSize: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* PNOTE - TODO - Keep it in reponsive row column tab */}
|
||||||
|
<Card style={{ width: "50%", margin: 20 }} bodyStyle={{ padding: 20 }}>
|
||||||
|
<Bar data={data} options={options} />
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (
|
||||||
|
state: StoreState,
|
||||||
|
): { usageData: usageDataItem[]; globalTime: GlobalTime } => {
|
||||||
|
return { usageData: state.usageDate, globalTime: state.globalTime };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UsageExplorer = connect(mapStateToProps, {
|
export const UsageExplorer = connect(mapStateToProps, {
|
||||||
getUsageData: getUsageData,
|
getUsageData: getUsageData,
|
||||||
})(_UsageExplorer);
|
})(_UsageExplorer);
|
||||||
|
@ -1 +1 @@
|
|||||||
export { UsageExplorer as default } from './UsageExplorer';
|
export { UsageExplorer as default } from "./UsageExplorer";
|
||||||
|
@ -1,34 +1,31 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from "react-dom";
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from "react-redux";
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from "redux";
|
||||||
import { ThemeSwitcherProvider } from "react-css-theme-switcher";
|
import { ThemeSwitcherProvider } from "react-css-theme-switcher";
|
||||||
import thunk from 'redux-thunk';
|
import thunk from "redux-thunk";
|
||||||
// import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
// import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
||||||
|
|
||||||
|
import AppWrapper from "./components/AppWrapper";
|
||||||
|
import "./assets/index.css";
|
||||||
import AppWrapper from './components/AppWrapper';
|
import { reducers } from "./reducers";
|
||||||
import './assets/index.css';
|
|
||||||
import { reducers } from './reducers';
|
|
||||||
// import Signup from './components/Signup';
|
// import Signup from './components/Signup';
|
||||||
|
|
||||||
const store = createStore(reducers, applyMiddleware(thunk))
|
const store = createStore(reducers, applyMiddleware(thunk));
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
dark: `${process.env.PUBLIC_URL}/dark-theme.css`,
|
dark: `${process.env.PUBLIC_URL}/dark-theme.css`,
|
||||||
light: `${process.env.PUBLIC_URL}/light-theme.css`,
|
light: `${process.env.PUBLIC_URL}/light-theme.css`,
|
||||||
};
|
};
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ThemeSwitcherProvider themeMap={themes} defaultTheme="dark">
|
<ThemeSwitcherProvider themeMap={themes} defaultTheme="dark">
|
||||||
|
<AppWrapper />
|
||||||
<AppWrapper />
|
{/* <App /> */}
|
||||||
{/* <App /> */}
|
</ThemeSwitcherProvider>
|
||||||
</ThemeSwitcherProvider>
|
</React.StrictMode>
|
||||||
</React.StrictMode>
|
</Provider>,
|
||||||
</Provider>,
|
document.querySelector("#root"),
|
||||||
document.querySelector('#root')
|
);
|
||||||
);
|
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { ActionTypes, Action, GlobalTime } from '../actions';
|
import { ActionTypes, Action, GlobalTime } from "../actions";
|
||||||
|
|
||||||
export const updateGlobalTimeReducer = (state:GlobalTime = {maxTime:Date.now()*1000000, minTime:(Date.now()-15*60*1000)*1000000}, action: Action) => {
|
export const updateGlobalTimeReducer = (
|
||||||
// Initial global state is time now and 15 minute interval
|
state: GlobalTime = {
|
||||||
switch (action.type){
|
maxTime: Date.now() * 1000000,
|
||||||
|
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
||||||
case ActionTypes.updateTimeInterval:
|
},
|
||||||
return action.payload;
|
action: Action,
|
||||||
default:
|
) => {
|
||||||
return state;
|
// Initial global state is time now and 15 minute interval
|
||||||
|
switch (action.type) {
|
||||||
}
|
case ActionTypes.updateTimeInterval:
|
||||||
|
return action.payload;
|
||||||
};
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,34 +1,48 @@
|
|||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from "redux";
|
||||||
import { traceResponseNew, spansWSameTraceIDResponse, servicesListItem, metricItem, topEndpointListItem, usageDataItem, GlobalTime, customMetricsItem, TraceFilters } from '../actions';
|
import {
|
||||||
import { updateGlobalTimeReducer } from './global';
|
traceResponseNew,
|
||||||
import { filteredTraceMetricsReducer, serviceMetricsReducer, serviceTableReducer, topEndpointsReducer } from './metrics';
|
spansWSameTraceIDResponse,
|
||||||
import { traceFiltersReducer, inputsReducer} from './traceFilters'
|
servicesListItem,
|
||||||
import { traceItemReducer, tracesReducer } from './traces'
|
metricItem,
|
||||||
import { usageDataReducer } from './usage'
|
topEndpointListItem,
|
||||||
|
usageDataItem,
|
||||||
|
GlobalTime,
|
||||||
|
customMetricsItem,
|
||||||
|
TraceFilters,
|
||||||
|
} from "../actions";
|
||||||
|
import { updateGlobalTimeReducer } from "./global";
|
||||||
|
import {
|
||||||
|
filteredTraceMetricsReducer,
|
||||||
|
serviceMetricsReducer,
|
||||||
|
serviceTableReducer,
|
||||||
|
topEndpointsReducer,
|
||||||
|
} from "./metrics";
|
||||||
|
import { traceFiltersReducer, inputsReducer } from "./traceFilters";
|
||||||
|
import { traceItemReducer, tracesReducer } from "./traces";
|
||||||
|
import { usageDataReducer } from "./usage";
|
||||||
|
|
||||||
export interface StoreState{
|
export interface StoreState {
|
||||||
traceFilters: TraceFilters,
|
traceFilters: TraceFilters;
|
||||||
inputTag: string,
|
inputTag: string;
|
||||||
traces: traceResponseNew,
|
traces: traceResponseNew;
|
||||||
traceItem: spansWSameTraceIDResponse ,
|
traceItem: spansWSameTraceIDResponse;
|
||||||
servicesList: servicesListItem[],
|
servicesList: servicesListItem[];
|
||||||
serviceMetrics:metricItem[],
|
serviceMetrics: metricItem[];
|
||||||
topEndpointsList:topEndpointListItem[],
|
topEndpointsList: topEndpointListItem[];
|
||||||
usageDate:usageDataItem[],
|
usageDate: usageDataItem[];
|
||||||
globalTime:GlobalTime,
|
globalTime: GlobalTime;
|
||||||
filteredTraceMetrics:customMetricsItem[],
|
filteredTraceMetrics: customMetricsItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reducers = combineReducers<StoreState>({
|
export const reducers = combineReducers<StoreState>({
|
||||||
traceFilters: traceFiltersReducer,
|
traceFilters: traceFiltersReducer,
|
||||||
inputTag: inputsReducer,
|
inputTag: inputsReducer,
|
||||||
traces: tracesReducer,
|
traces: tracesReducer,
|
||||||
traceItem: traceItemReducer,
|
traceItem: traceItemReducer,
|
||||||
servicesList: serviceTableReducer,
|
servicesList: serviceTableReducer,
|
||||||
serviceMetrics: serviceMetricsReducer,
|
serviceMetrics: serviceMetricsReducer,
|
||||||
topEndpointsList: topEndpointsReducer,
|
topEndpointsList: topEndpointsReducer,
|
||||||
usageDate: usageDataReducer,
|
usageDate: usageDataReducer,
|
||||||
globalTime: updateGlobalTimeReducer,
|
globalTime: updateGlobalTimeReducer,
|
||||||
filteredTraceMetrics:filteredTraceMetricsReducer,
|
filteredTraceMetrics: filteredTraceMetricsReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,38 +1,79 @@
|
|||||||
import { ActionTypes, Action, servicesListItem, metricItem, topEndpointListItem, customMetricsItem} from '../actions'
|
import {
|
||||||
|
ActionTypes,
|
||||||
|
Action,
|
||||||
|
servicesListItem,
|
||||||
|
metricItem,
|
||||||
|
topEndpointListItem,
|
||||||
|
customMetricsItem,
|
||||||
|
} from "../actions";
|
||||||
|
|
||||||
export const serviceTableReducer = (state: servicesListItem[] = [{"serviceName": '', "p99": 0, "avgDuration": 0, "numCalls": 0, "callRate": 0, "numErrors": 0, "errorRate": 0,}], action: Action) => {
|
export const serviceTableReducer = (
|
||||||
switch (action.type) {
|
state: servicesListItem[] = [
|
||||||
case ActionTypes.getServicesList:
|
{
|
||||||
return action.payload;
|
serviceName: "",
|
||||||
default:
|
p99: 0,
|
||||||
return state;
|
avgDuration: 0,
|
||||||
}
|
numCalls: 0,
|
||||||
};
|
callRate: 0,
|
||||||
|
numErrors: 0,
|
||||||
|
errorRate: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
action: Action,
|
||||||
|
) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.getServicesList:
|
||||||
|
return action.payload;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const serviceMetricsReducer = (state: metricItem[] = [{"timestamp":0,"p50":0,"p90":0,"p99":0,"numCalls":0,"callRate":0.0,"numErrors":0,"errorRate":0}], action: Action) => {
|
export const serviceMetricsReducer = (
|
||||||
switch (action.type) {
|
state: metricItem[] = [
|
||||||
case ActionTypes.getServiceMetrics:
|
{
|
||||||
return action.payload;
|
timestamp: 0,
|
||||||
default:
|
p50: 0,
|
||||||
return state;
|
p90: 0,
|
||||||
}
|
p99: 0,
|
||||||
};
|
numCalls: 0,
|
||||||
|
callRate: 0.0,
|
||||||
|
numErrors: 0,
|
||||||
|
errorRate: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
action: Action,
|
||||||
|
) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.getServiceMetrics:
|
||||||
|
return action.payload;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const topEndpointsReducer = (state: topEndpointListItem[] = [{"p50":0,"p90":0,"p99":0,"numCalls":0,"name":''}], action: Action) => {
|
export const topEndpointsReducer = (
|
||||||
switch (action.type) {
|
state: topEndpointListItem[] = [
|
||||||
case ActionTypes.getTopEndpoints:
|
{ p50: 0, p90: 0, p99: 0, numCalls: 0, name: "" },
|
||||||
return action.payload;
|
],
|
||||||
default:
|
action: Action,
|
||||||
return state;
|
) => {
|
||||||
}
|
switch (action.type) {
|
||||||
};
|
case ActionTypes.getTopEndpoints:
|
||||||
|
return action.payload;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filteredTraceMetricsReducer = (
|
||||||
export const filteredTraceMetricsReducer = (state: customMetricsItem[] = [{"timestamp": 0, "value": 0}], action: Action) => {
|
state: customMetricsItem[] = [{ timestamp: 0, value: 0 }],
|
||||||
switch (action.type) {
|
action: Action,
|
||||||
case ActionTypes.getFilteredTraceMetrics:
|
) => {
|
||||||
return action.payload;
|
switch (action.type) {
|
||||||
default:
|
case ActionTypes.getFilteredTraceMetrics:
|
||||||
return state;
|
return action.payload;
|
||||||
}
|
default:
|
||||||
};
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,26 +1,35 @@
|
|||||||
import { ActionTypes, TraceFilters, updateInputTagAction, updateTraceFiltersAction } from '../actions';
|
import {
|
||||||
|
ActionTypes,
|
||||||
|
TraceFilters,
|
||||||
|
updateInputTagAction,
|
||||||
|
updateTraceFiltersAction,
|
||||||
|
} from "../actions";
|
||||||
|
|
||||||
export const traceFiltersReducer = (state:TraceFilters = {'service':'', 'tags':[],'operation':'','latency':{'min':'','max':''}}, action: updateTraceFiltersAction) => {
|
export const traceFiltersReducer = (
|
||||||
|
state: TraceFilters = {
|
||||||
switch (action.type){
|
service: "",
|
||||||
|
tags: [],
|
||||||
case ActionTypes.updateTraceFilters:
|
operation: "",
|
||||||
return action.payload;
|
latency: { min: "", max: "" },
|
||||||
default:
|
},
|
||||||
return state;
|
action: updateTraceFiltersAction,
|
||||||
|
) => {
|
||||||
}
|
switch (action.type) {
|
||||||
|
case ActionTypes.updateTraceFilters:
|
||||||
|
return action.payload;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const inputsReducer = (state:string = '', action:updateInputTagAction) => {
|
export const inputsReducer = (
|
||||||
|
state: string = "",
|
||||||
switch (action.type){
|
action: updateInputTagAction,
|
||||||
case ActionTypes.updateInput:
|
) => {
|
||||||
return action.payload;
|
switch (action.type) {
|
||||||
default:
|
case ActionTypes.updateInput:
|
||||||
return state;
|
return action.payload;
|
||||||
|
default:
|
||||||
}
|
return state;
|
||||||
|
}
|
||||||
}
|
};
|
||||||
|
@ -1,21 +1,33 @@
|
|||||||
import { ActionTypes, Action, traceResponseNew, spanList, spansWSameTraceIDResponse } from '../actions';
|
import {
|
||||||
|
ActionTypes,
|
||||||
|
Action,
|
||||||
|
traceResponseNew,
|
||||||
|
spanList,
|
||||||
|
spansWSameTraceIDResponse,
|
||||||
|
} from "../actions";
|
||||||
|
|
||||||
// PNOTE - Initializing is a must for state variable otherwise it gives an error in reducer
|
// PNOTE - Initializing is a must for state variable otherwise it gives an error in reducer
|
||||||
var spanlistinstance :spanList ={ events: [], segmentID: '', columns: []} ;
|
var spanlistinstance: spanList = { events: [], segmentID: "", columns: [] };
|
||||||
export const tracesReducer = (state: traceResponseNew = {"0": spanlistinstance} , action: Action) => {
|
export const tracesReducer = (
|
||||||
switch (action.type) {
|
state: traceResponseNew = { "0": spanlistinstance },
|
||||||
case ActionTypes.fetchTraces:
|
action: Action,
|
||||||
return action.payload;
|
) => {
|
||||||
default:
|
switch (action.type) {
|
||||||
return state;
|
case ActionTypes.fetchTraces:
|
||||||
}
|
return action.payload;
|
||||||
};
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const traceItemReducer = (state: spansWSameTraceIDResponse = {"0": spanlistinstance}, action: Action) => {
|
export const traceItemReducer = (
|
||||||
switch (action.type) {
|
state: spansWSameTraceIDResponse = { "0": spanlistinstance },
|
||||||
case ActionTypes.fetchTraceItem:
|
action: Action,
|
||||||
return action.payload;
|
) => {
|
||||||
default:
|
switch (action.type) {
|
||||||
return state;
|
case ActionTypes.fetchTraceItem:
|
||||||
}
|
return action.payload;
|
||||||
};
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { ActionTypes, Action, usageDataItem, } from '../actions'
|
import { ActionTypes, Action, usageDataItem } from "../actions";
|
||||||
|
|
||||||
export const usageDataReducer = (state: usageDataItem[] = [{"timestamp": 0, "count": 0, }], action: Action) => {
|
export const usageDataReducer = (
|
||||||
switch (action.type) {
|
state: usageDataItem[] = [{ timestamp: 0, count: 0 }],
|
||||||
case ActionTypes.getUsageData:
|
action: Action,
|
||||||
return action.payload;
|
) => {
|
||||||
default:
|
switch (action.type) {
|
||||||
return state;
|
case ActionTypes.getUsageData:
|
||||||
}
|
return action.payload;
|
||||||
};
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// dark-theme.less
|
// dark-theme.less
|
||||||
|
|
||||||
@import '~antd/lib/style/color/colorPalette.less';
|
@import "~antd/lib/style/color/colorPalette.less";
|
||||||
@import '~antd/dist/antd.less';
|
@import "~antd/dist/antd.less";
|
||||||
@import '~antd/lib/style/themes/dark.less';
|
@import "~antd/lib/style/themes/dark.less";
|
||||||
|
|
||||||
// @primary-color: #00adb5;
|
// @primary-color: #00adb5;
|
||||||
// @border-radius-base: 4px;
|
// @border-radius-base: 4px;
|
||||||
@ -15,4 +15,4 @@
|
|||||||
// @table-header-sort-active-bg: #424242;
|
// @table-header-sort-active-bg: #424242;
|
||||||
// @card-skeleton-bg: #424242;
|
// @card-skeleton-bg: #424242;
|
||||||
// @skeleton-color: #424242;
|
// @skeleton-color: #424242;
|
||||||
// @table-header-sort-active-bg: #424242;
|
// @table-header-sort-active-bg: #424242;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/* light-theme.less */
|
/* light-theme.less */
|
||||||
|
|
||||||
@import '~antd/lib/style/color/colorPalette.less';
|
@import "~antd/lib/style/color/colorPalette.less";
|
||||||
@import '~antd/dist/antd.less';
|
@import "~antd/dist/antd.less";
|
||||||
@import '~antd/lib/style/themes/default.less';
|
@import "~antd/lib/style/themes/default.less";
|
||||||
|
|
||||||
/* These are shared variables that can be extracted to their own file */
|
/* These are shared variables that can be extracted to their own file */
|
||||||
@primary-color: #00adb5;
|
@primary-color: #00adb5;
|
||||||
@border-radius-base: 4px;
|
@border-radius-base: 4px;
|
||||||
|
2
frontend/src/typings/d3-tip.d.ts
vendored
2
frontend/src/typings/d3-tip.d.ts
vendored
@ -1 +1 @@
|
|||||||
declare module 'd3-tip';
|
declare module "d3-tip";
|
||||||
|
74
frontend/src/typings/react-graph-vis.d.ts
vendored
74
frontend/src/typings/react-graph-vis.d.ts
vendored
@ -1,39 +1,39 @@
|
|||||||
//You must first install the vis and react types 'npm install --save-dev @types/vis @types/react'
|
//You must first install the vis and react types 'npm install --save-dev @types/vis @types/react'
|
||||||
declare module "react-graph-vis" {
|
declare module "react-graph-vis" {
|
||||||
import { Network, NetworkEvents, Options, Node, Edge, DataSet } from "vis";
|
import { Network, NetworkEvents, Options, Node, Edge, DataSet } from "vis";
|
||||||
import { Component } from "react";
|
import { Component } from "react";
|
||||||
|
|
||||||
export { Network, NetworkEvents, Options, Node, Edge, DataSet } from "vis";
|
export { Network, NetworkEvents, Options, Node, Edge, DataSet } from "vis";
|
||||||
|
|
||||||
export interface graphEvents {
|
export interface graphEvents {
|
||||||
[event: NetworkEvents]: (params?: any) => void;
|
[event: NetworkEvents]: (params?: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Doesn't appear that this module supports passing in a vis.DataSet directly. Once it does graph can just use the Data object from vis.
|
//Doesn't appear that this module supports passing in a vis.DataSet directly. Once it does graph can just use the Data object from vis.
|
||||||
export interface graphData {
|
export interface graphData {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
edges: Edge[];
|
edges: Edge[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkGraphProps {
|
export interface NetworkGraphProps {
|
||||||
graph: graphData;
|
graph: graphData;
|
||||||
options?: Options;
|
options?: Options;
|
||||||
events?: graphEvents;
|
events?: graphEvents;
|
||||||
getNetwork?: (network: Network) => void;
|
getNetwork?: (network: Network) => void;
|
||||||
identifier?: string;
|
identifier?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
getNodes?: (nodes: DataSet) => void;
|
getNodes?: (nodes: DataSet) => void;
|
||||||
getEdges?: (edges: DataSet) => void;
|
getEdges?: (edges: DataSet) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkGraphState {
|
export interface NetworkGraphState {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NetworkGraph extends Component<
|
export default class NetworkGraph extends Component<
|
||||||
NetworkGraphProps,
|
NetworkGraphProps,
|
||||||
NetworkGraphState
|
NetworkGraphState
|
||||||
> {
|
> {
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from "history";
|
||||||
export default createBrowserHistory();
|
export default createBrowserHistory();
|
||||||
|
@ -1,106 +1,100 @@
|
|||||||
import { pushDStree, span, RefItem } from '../actions';
|
import { pushDStree, span, RefItem } from "../actions";
|
||||||
// PNOTE - should the data be taken from redux or only through props? - Directly as arguments
|
// PNOTE - should the data be taken from redux or only through props? - Directly as arguments
|
||||||
|
|
||||||
|
export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
|
||||||
|
// Initializing tree. What should be returned is trace is empty? We should have better error handling
|
||||||
|
let tree: pushDStree = {
|
||||||
|
id: "empty",
|
||||||
|
name: "default",
|
||||||
|
value: 0,
|
||||||
|
time: 0,
|
||||||
|
startTime: 0,
|
||||||
|
tags: [],
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// let spans :spanItem[]= trace.spans;
|
||||||
|
|
||||||
|
if (spanlist) {
|
||||||
|
// Create a dict with spanIDs as keys
|
||||||
|
// PNOTE
|
||||||
|
// Can we now assign different strings as id - Yes
|
||||||
|
// https://stackoverflow.com/questions/15877362/declare-and-initialize-a-dictionary-in-typescript
|
||||||
|
|
||||||
export const spanToTreeUtil = (spanlist :span[]) :pushDStree => {
|
let mapped_array: { [id: string]: span } = {};
|
||||||
|
|
||||||
// Initializing tree. What should be returned is trace is empty? We should have better error handling
|
for (let i = 0; i < spanlist.length; i++) {
|
||||||
let tree :pushDStree ={id:'empty', name:'default', value:0, time: 0, startTime:0, tags:[], children:[]};
|
mapped_array[spanlist[i][1]] = spanlist[i];
|
||||||
|
mapped_array[spanlist[i][1]][10] = [];
|
||||||
|
}
|
||||||
|
|
||||||
// let spans :spanItem[]= trace.spans;
|
for (let id in mapped_array) {
|
||||||
|
let child_span = mapped_array[id];
|
||||||
|
|
||||||
if(spanlist){
|
//mapping tags to new structure
|
||||||
|
let tags_temp = [];
|
||||||
|
if (child_span[7] !== null && child_span[8] !== null) {
|
||||||
|
if (
|
||||||
|
typeof child_span[7] === "string" &&
|
||||||
|
typeof child_span[8] === "string"
|
||||||
|
) {
|
||||||
|
tags_temp.push({ key: child_span[7], value: child_span[8] });
|
||||||
|
} else if (child_span[7].length > 0 && child_span[8].length > 0) {
|
||||||
|
for (let j = 0; j < child_span[7].length; j++) {
|
||||||
|
tags_temp.push({ key: child_span[7][j], value: child_span[8][j] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a dict with spanIDs as keys
|
let push_object: pushDStree = {
|
||||||
// PNOTE
|
id: child_span[1],
|
||||||
// Can we now assign different strings as id - Yes
|
name: child_span[3] + ": " + child_span[4],
|
||||||
// https://stackoverflow.com/questions/15877362/declare-and-initialize-a-dictionary-in-typescript
|
value: parseInt(child_span[6]),
|
||||||
|
time: parseInt(child_span[6]),
|
||||||
|
startTime: child_span[0],
|
||||||
|
tags: tags_temp,
|
||||||
|
children: mapped_array[id][10],
|
||||||
|
};
|
||||||
|
let referencesArr = mapped_array[id][9];
|
||||||
|
let refArray = [];
|
||||||
|
if (typeof referencesArr === "string") {
|
||||||
|
refArray.push(referencesArr);
|
||||||
|
} else {
|
||||||
|
refArray = referencesArr;
|
||||||
|
}
|
||||||
|
let references: RefItem[] = [];
|
||||||
|
|
||||||
let mapped_array : {[id: string] : span;} = {};
|
refArray.forEach((element) => {
|
||||||
|
element = element
|
||||||
|
.replaceAll("{", "")
|
||||||
|
.replaceAll("}", "")
|
||||||
|
.replaceAll(" ", "");
|
||||||
|
let arr = element.split(",");
|
||||||
|
let refItem = { traceID: "", spanID: "", refType: "" };
|
||||||
|
arr.forEach((obj) => {
|
||||||
|
let arr2 = obj.split("=");
|
||||||
|
if (arr2[0] === "TraceId") {
|
||||||
|
refItem["traceID"] = arr2[1];
|
||||||
|
} else if (arr2[0] === "SpanId") {
|
||||||
|
refItem["spanID"] = arr2[1];
|
||||||
|
} else if (arr2[0] === "RefType") {
|
||||||
|
refItem["refType"] = arr2[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for(let i=0; i<spanlist.length; i++){
|
references.push(refItem);
|
||||||
mapped_array[spanlist[i][1]] = spanlist[i];
|
});
|
||||||
mapped_array[spanlist[i][1]][10] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let id in mapped_array){
|
if (references.length !== 0 && references[0].spanID.length !== 0) {
|
||||||
let child_span = mapped_array[id];
|
if (references[0].refType === "CHILD_OF") {
|
||||||
|
let parentID = references[0].spanID;
|
||||||
|
mapped_array[parentID][10].push(push_object);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tree = push_object;
|
||||||
|
}
|
||||||
|
} // end of for loop
|
||||||
|
} // end of if(spans)
|
||||||
|
|
||||||
//mapping tags to new structure
|
return tree;
|
||||||
let tags_temp = [];
|
};
|
||||||
if (child_span[7]!==null && child_span[8]!==null )
|
|
||||||
{
|
|
||||||
if (typeof(child_span[7])==='string' && typeof(child_span[8])==='string')
|
|
||||||
{
|
|
||||||
tags_temp.push({key:child_span[7],value:child_span[8]})
|
|
||||||
|
|
||||||
} else if (child_span[7].length>0 && child_span[8].length>0)
|
|
||||||
{
|
|
||||||
for(let j=0;j<child_span[7].length; j++)
|
|
||||||
{
|
|
||||||
tags_temp.push({key:child_span[7][j],value:child_span[8][j]})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let push_object :pushDStree = {
|
|
||||||
id: child_span[1],
|
|
||||||
name: child_span[3] + ": " + child_span[4],
|
|
||||||
value: parseInt(child_span[6]),
|
|
||||||
time: parseInt(child_span[6]),
|
|
||||||
startTime: child_span[0],
|
|
||||||
tags:tags_temp,
|
|
||||||
children: mapped_array[id][10]
|
|
||||||
}
|
|
||||||
let referencesArr = mapped_array[id][9]
|
|
||||||
let refArray = []
|
|
||||||
if(typeof(referencesArr) === "string"){
|
|
||||||
refArray.push(referencesArr)
|
|
||||||
}else{
|
|
||||||
refArray = referencesArr
|
|
||||||
}
|
|
||||||
let references :RefItem[] = [];
|
|
||||||
|
|
||||||
refArray.forEach(element => {
|
|
||||||
element = element.replaceAll("{", "").replaceAll("}", "").replaceAll(" ", "")
|
|
||||||
let arr = element.split(",")
|
|
||||||
let refItem = {"traceID": "", "spanID": "", "refType": ""};
|
|
||||||
arr.forEach(obj => {
|
|
||||||
let arr2 = obj.split("=")
|
|
||||||
if(arr2[0]==="TraceId"){
|
|
||||||
refItem["traceID"] = arr2[1];
|
|
||||||
}else if(arr2[0]==="SpanId"){
|
|
||||||
refItem["spanID"] = arr2[1];
|
|
||||||
}else if(arr2[0]==="RefType"){
|
|
||||||
refItem["refType"] = arr2[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
references.push(refItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(references.length !== 0 && references[0].spanID.length !== 0){
|
|
||||||
if(references[0].refType === "CHILD_OF"){
|
|
||||||
let parentID = references[0].spanID;
|
|
||||||
mapped_array[parentID][10].push(push_object);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else{
|
|
||||||
tree = push_object;
|
|
||||||
}
|
|
||||||
}// end of for loop
|
|
||||||
|
|
||||||
}// end of if(spans)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return(tree)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/",
|
"outDir": "./dist/",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
"allowSyntheticDefaultImports": true,
|
||||||
"dom.iterable",
|
"noFallthroughCasesInSwitch": true,
|
||||||
"esnext"
|
"moduleResolution": "node",
|
||||||
],
|
"resolveJsonModule": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"isolatedModules": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noEmit": true
|
||||||
"moduleResolution": "node",
|
},
|
||||||
"resolveJsonModule": true,
|
"include": ["src"]
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -2672,7 +2672,7 @@ ansi-cyan@^0.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-wrap "0.1.0"
|
ansi-wrap "0.1.0"
|
||||||
|
|
||||||
ansi-escapes@^4.2.1, ansi-escapes@^4.3.1:
|
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
|
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
|
||||||
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
|
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
|
||||||
@ -3036,6 +3036,11 @@ astral-regex@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
|
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
|
||||||
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
|
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
|
||||||
|
|
||||||
|
astral-regex@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
|
||||||
|
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
|
||||||
|
|
||||||
async-done@^1.2.0, async-done@^1.2.2:
|
async-done@^1.2.0, async-done@^1.2.2:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2"
|
resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2"
|
||||||
@ -3958,6 +3963,14 @@ cli-cursor@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
restore-cursor "^3.1.0"
|
restore-cursor "^3.1.0"
|
||||||
|
|
||||||
|
cli-truncate@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
|
||||||
|
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
|
||||||
|
dependencies:
|
||||||
|
slice-ansi "^3.0.0"
|
||||||
|
string-width "^4.2.0"
|
||||||
|
|
||||||
cli-width@^3.0.0:
|
cli-width@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
|
||||||
@ -4145,6 +4158,11 @@ commander@^4.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||||
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||||
|
|
||||||
|
commander@^6.2.0:
|
||||||
|
version "6.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||||
|
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||||
|
|
||||||
common-tags@^1.8.0:
|
common-tags@^1.8.0:
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
|
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
|
||||||
@ -4155,6 +4173,11 @@ commondir@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||||
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
|
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
|
||||||
|
|
||||||
|
compare-versions@^3.6.0:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
|
||||||
|
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
|
||||||
|
|
||||||
component-emitter@^1.2.1, component-emitter@^1.3.0:
|
component-emitter@^1.2.1, component-emitter@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||||
@ -5179,6 +5202,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
|
debug@^4.2.0:
|
||||||
|
version "4.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||||
|
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.2.0:
|
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||||
@ -5627,7 +5657,7 @@ enhanced-resolve@^4.3.0:
|
|||||||
memory-fs "^0.5.0"
|
memory-fs "^0.5.0"
|
||||||
tapable "^1.0.0"
|
tapable "^1.0.0"
|
||||||
|
|
||||||
enquirer@^2.3.5:
|
enquirer@^2.3.5, enquirer@^2.3.6:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||||
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
||||||
@ -6067,7 +6097,7 @@ execa@^1.0.0:
|
|||||||
signal-exit "^3.0.0"
|
signal-exit "^3.0.0"
|
||||||
strip-eof "^1.0.0"
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
execa@^4.0.0:
|
execa@^4.0.0, execa@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
||||||
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
|
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
|
||||||
@ -6302,7 +6332,7 @@ figgy-pudding@^3.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
|
||||||
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
|
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
|
||||||
|
|
||||||
figures@^3.0.0:
|
figures@^3.0.0, figures@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
|
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
|
||||||
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
|
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
|
||||||
@ -6412,6 +6442,21 @@ find-up@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
locate-path "^3.0.0"
|
locate-path "^3.0.0"
|
||||||
|
|
||||||
|
find-up@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
|
||||||
|
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
|
||||||
|
dependencies:
|
||||||
|
locate-path "^6.0.0"
|
||||||
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
|
find-versions@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965"
|
||||||
|
integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==
|
||||||
|
dependencies:
|
||||||
|
semver-regex "^3.1.2"
|
||||||
|
|
||||||
findup-sync@^2.0.0:
|
findup-sync@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc"
|
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc"
|
||||||
@ -7250,6 +7295,22 @@ human-signals@^1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||||
|
|
||||||
|
husky@4.3.8:
|
||||||
|
version "4.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d"
|
||||||
|
integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.0.0"
|
||||||
|
ci-info "^2.0.0"
|
||||||
|
compare-versions "^3.6.0"
|
||||||
|
cosmiconfig "^7.0.0"
|
||||||
|
find-versions "^4.0.0"
|
||||||
|
opencollective-postinstall "^2.0.2"
|
||||||
|
pkg-dir "^5.0.0"
|
||||||
|
please-upgrade-node "^3.2.0"
|
||||||
|
slash "^3.0.0"
|
||||||
|
which-pm-runs "^1.0.0"
|
||||||
|
|
||||||
hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3:
|
hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
|
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
|
||||||
@ -8771,6 +8832,41 @@ lines-and-columns@^1.1.6:
|
|||||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||||
|
|
||||||
|
lint-staged@10.5.3:
|
||||||
|
version "10.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.3.tgz#c682838b3eadd4c864d1022da05daa0912fb1da5"
|
||||||
|
integrity sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.1.0"
|
||||||
|
cli-truncate "^2.1.0"
|
||||||
|
commander "^6.2.0"
|
||||||
|
cosmiconfig "^7.0.0"
|
||||||
|
debug "^4.2.0"
|
||||||
|
dedent "^0.7.0"
|
||||||
|
enquirer "^2.3.6"
|
||||||
|
execa "^4.1.0"
|
||||||
|
listr2 "^3.2.2"
|
||||||
|
log-symbols "^4.0.0"
|
||||||
|
micromatch "^4.0.2"
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
please-upgrade-node "^3.2.0"
|
||||||
|
string-argv "0.3.1"
|
||||||
|
stringify-object "^3.3.0"
|
||||||
|
|
||||||
|
listr2@^3.2.2:
|
||||||
|
version "3.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.2.3.tgz#ef9e0d790862f038dde8a9837be552b1adfd1c07"
|
||||||
|
integrity sha512-vUb80S2dSUi8YxXahO8/I/s29GqnOL8ozgHVLjfWQXa03BNEeS1TpBLjh2ruaqq5ufx46BRGvfymdBSuoXET5w==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.1.0"
|
||||||
|
cli-truncate "^2.1.0"
|
||||||
|
figures "^3.2.0"
|
||||||
|
indent-string "^4.0.0"
|
||||||
|
log-update "^4.0.0"
|
||||||
|
p-map "^4.0.0"
|
||||||
|
rxjs "^6.6.3"
|
||||||
|
through "^2.3.8"
|
||||||
|
|
||||||
load-json-file@^1.0.0:
|
load-json-file@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
|
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
|
||||||
@ -8847,6 +8943,13 @@ locate-path@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^4.1.0"
|
p-locate "^4.1.0"
|
||||||
|
|
||||||
|
locate-path@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
||||||
|
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
|
||||||
|
dependencies:
|
||||||
|
p-locate "^5.0.0"
|
||||||
|
|
||||||
lodash._reinterpolate@^3.0.0:
|
lodash._reinterpolate@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
@ -8927,6 +9030,23 @@ lodash.uniq@^4.3.0, lodash.uniq@^4.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||||
|
|
||||||
|
log-symbols@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
|
||||||
|
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
|
||||||
|
dependencies:
|
||||||
|
chalk "^4.0.0"
|
||||||
|
|
||||||
|
log-update@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
|
||||||
|
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
|
||||||
|
dependencies:
|
||||||
|
ansi-escapes "^4.3.0"
|
||||||
|
cli-cursor "^3.1.0"
|
||||||
|
slice-ansi "^4.0.0"
|
||||||
|
wrap-ansi "^6.2.0"
|
||||||
|
|
||||||
loglevel@^1.6.8:
|
loglevel@^1.6.8:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
|
||||||
@ -9756,6 +9876,11 @@ open@^7.0.2:
|
|||||||
is-docker "^2.0.0"
|
is-docker "^2.0.0"
|
||||||
is-wsl "^2.1.1"
|
is-wsl "^2.1.1"
|
||||||
|
|
||||||
|
opencollective-postinstall@^2.0.2:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
|
||||||
|
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
|
||||||
|
|
||||||
opn@^5.5.0:
|
opn@^5.5.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
|
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
|
||||||
@ -9883,6 +10008,13 @@ p-locate@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit "^2.2.0"
|
p-limit "^2.2.0"
|
||||||
|
|
||||||
|
p-locate@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
|
||||||
|
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
|
||||||
|
dependencies:
|
||||||
|
p-limit "^3.0.2"
|
||||||
|
|
||||||
p-map@^2.0.0:
|
p-map@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
|
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
|
||||||
@ -10192,6 +10324,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^4.0.0"
|
find-up "^4.0.0"
|
||||||
|
|
||||||
|
pkg-dir@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760"
|
||||||
|
integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==
|
||||||
|
dependencies:
|
||||||
|
find-up "^5.0.0"
|
||||||
|
|
||||||
pkg-up@3.1.0:
|
pkg-up@3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
|
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
|
||||||
@ -10199,6 +10338,13 @@ pkg-up@3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^3.0.0"
|
find-up "^3.0.0"
|
||||||
|
|
||||||
|
please-upgrade-node@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
||||||
|
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
|
||||||
|
dependencies:
|
||||||
|
semver-compare "^1.0.0"
|
||||||
|
|
||||||
plugin-error@^0.1.2:
|
plugin-error@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
|
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
|
||||||
@ -10940,6 +11086,11 @@ prepend-http@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||||
|
|
||||||
|
prettier@2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
|
||||||
|
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
|
||||||
|
|
||||||
pretty-bytes@^5.3.0:
|
pretty-bytes@^5.3.0:
|
||||||
version "5.4.1"
|
version "5.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b"
|
||||||
@ -12496,7 +12647,7 @@ rw@1:
|
|||||||
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
|
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
|
||||||
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
|
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
|
||||||
|
|
||||||
rxjs@^6.6.0:
|
rxjs@^6.6.0, rxjs@^6.6.3:
|
||||||
version "6.6.3"
|
version "6.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
||||||
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
||||||
@ -12627,6 +12778,11 @@ selfsigned@^1.10.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
node-forge "^0.10.0"
|
node-forge "^0.10.0"
|
||||||
|
|
||||||
|
semver-compare@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||||
|
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
|
||||||
|
|
||||||
semver-greatest-satisfied-range@^1.1.0:
|
semver-greatest-satisfied-range@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
|
resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
|
||||||
@ -12634,6 +12790,11 @@ semver-greatest-satisfied-range@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sver-compat "^1.5.0"
|
sver-compat "^1.5.0"
|
||||||
|
|
||||||
|
semver-regex@^3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807"
|
||||||
|
integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==
|
||||||
|
|
||||||
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
|
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
@ -12838,6 +12999,24 @@ slice-ansi@^2.1.0:
|
|||||||
astral-regex "^1.0.0"
|
astral-regex "^1.0.0"
|
||||||
is-fullwidth-code-point "^2.0.0"
|
is-fullwidth-code-point "^2.0.0"
|
||||||
|
|
||||||
|
slice-ansi@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
|
||||||
|
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
astral-regex "^2.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
|
||||||
|
slice-ansi@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
|
||||||
|
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
astral-regex "^2.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
|
||||||
snapdragon-node@^2.0.1:
|
snapdragon-node@^2.0.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
|
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
|
||||||
@ -13130,6 +13309,11 @@ strict-uri-encode@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||||
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
|
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
|
||||||
|
|
||||||
|
string-argv@0.3.1:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
|
||||||
|
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
|
||||||
|
|
||||||
string-convert@^0.2.0:
|
string-convert@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
|
resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
|
||||||
@ -13530,7 +13714,7 @@ through2@^2.0.0, through2@^2.0.3, through2@~2.0.0:
|
|||||||
readable-stream "~2.3.6"
|
readable-stream "~2.3.6"
|
||||||
xtend "~4.0.1"
|
xtend "~4.0.1"
|
||||||
|
|
||||||
through@^2.3.6:
|
through@^2.3.6, through@^2.3.8:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||||
@ -14444,6 +14628,11 @@ which-module@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||||
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||||
|
|
||||||
|
which-pm-runs@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
|
||||||
|
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
|
||||||
|
|
||||||
which@^1.2.14, which@^1.2.9, which@^1.3.1:
|
which@^1.2.14, which@^1.2.9, which@^1.3.1:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user