mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-08 13:08:59 +08:00
Merge pull request #6 from himanshu-source21/main
Fix Sig-13, Fix-11, Logo and lint
This commit is contained in:
commit
cd16bf43bd
4
.gitignore
vendored
4
.gitignore
vendored
@ -19,9 +19,13 @@ frontend/.yarnclean
|
||||
frontend/npm-debug.log*
|
||||
frontend/yarn-debug.log*
|
||||
frontend/yarn-error.log*
|
||||
frontend/src/constants/env.ts
|
||||
|
||||
.idea
|
||||
|
||||
**/.vscode
|
||||
*.tgz
|
||||
**/build
|
||||
**/storage
|
||||
**/locust-scripts/__pycache__/
|
||||
|
||||
|
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,19 +1,18 @@
|
||||
# stage1 as builder
|
||||
FROM node:14-alpine as builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
# copy the package.json to install dependencies
|
||||
COPY package.json ./
|
||||
|
||||
# Install the dependencies and make the folder
|
||||
RUN npm install && mkdir /react-ui && mv ./node_modules ./react-ui
|
||||
|
||||
WORKDIR /react-ui
|
||||
RUN yarn install
|
||||
|
||||
COPY . .
|
||||
|
||||
# Build the project and copy the files
|
||||
RUN npm run build
|
||||
|
||||
RUN yarn build
|
||||
|
||||
FROM nginx:1.15-alpine
|
||||
|
||||
@ -25,7 +24,7 @@ COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
# Copy from the stahg 1
|
||||
COPY --from=builder /react-ui/build /usr/share/nginx/html
|
||||
COPY --from=builder /frontend/build /usr/share/nginx/html
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
|
@ -1,3 +1,27 @@
|
||||
|
||||
# Docker
|
||||
**Building image**
|
||||
|
||||
```docker-compose up``
|
||||
/ This will also run
|
||||
|
||||
or
|
||||
```docker build . -t tagname```
|
||||
|
||||
|
||||
|
||||
**Tag to remote url- Introduce versinoing later on**
|
||||
|
||||
```
|
||||
docker tag signoz/frontend:latest 7296823551/signoz:latest
|
||||
```
|
||||
|
||||
**Running locally**
|
||||
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
7
frontend/docker-compose.yml
Normal file
7
frontend/docker-compose.yml
Normal file
@ -0,0 +1,7 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
image: signoz/frontend:latest
|
||||
ports:
|
||||
- "3000:3000"
|
@ -1,21 +1,21 @@
|
||||
const gulp = require('gulp')
|
||||
const gulpless = require('gulp-less')
|
||||
const postcss = require('gulp-postcss')
|
||||
const debug = require('gulp-debug')
|
||||
var csso = require('gulp-csso')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const NpmImportPlugin = require('less-plugin-npm-import')
|
||||
const gulp = require("gulp");
|
||||
const gulpless = require("gulp-less");
|
||||
const postcss = require("gulp-postcss");
|
||||
const debug = require("gulp-debug");
|
||||
var csso = require("gulp-csso");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const NpmImportPlugin = require("less-plugin-npm-import");
|
||||
|
||||
gulp.task('less', function () {
|
||||
const plugins = [autoprefixer()]
|
||||
gulp.task("less", function () {
|
||||
const plugins = [autoprefixer()];
|
||||
|
||||
return gulp
|
||||
.src('src/themes/*-theme.less')
|
||||
.pipe(debug({title: 'Less files:'}))
|
||||
.src("src/themes/*-theme.less")
|
||||
.pipe(debug({ title: "Less files:" }))
|
||||
.pipe(
|
||||
gulpless({
|
||||
javascriptEnabled: true,
|
||||
plugins: [new NpmImportPlugin({prefix: '~'})],
|
||||
plugins: [new NpmImportPlugin({ prefix: "~" })],
|
||||
}),
|
||||
)
|
||||
.pipe(postcss(plugins))
|
||||
@ -24,5 +24,5 @@ gulp.task('less', function () {
|
||||
debug: true,
|
||||
}),
|
||||
)
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
.pipe(gulp.dest("./public"));
|
||||
});
|
||||
|
@ -51,7 +51,8 @@
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"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": [
|
||||
@ -81,7 +82,10 @@
|
||||
"gulp-debug": "^4.0.0",
|
||||
"gulp-less": "^4.0.1",
|
||||
"gulp-postcss": "^9.0.0",
|
||||
"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
@ -5,12 +5,14 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<meta name="description" content="Web site created using create-react-app" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<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">
|
||||
<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"
|
||||
/>
|
||||
<!--
|
||||
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/
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,10 +1,8 @@
|
||||
import { ActionTypes } from './types';
|
||||
import { Moment } from 'moment'
|
||||
|
||||
import { ActionTypes } from "./types";
|
||||
import { Moment } from "moment";
|
||||
|
||||
export type DateTimeRangeType = [Moment | null, Moment | null] | null;
|
||||
|
||||
|
||||
export interface GlobalTime {
|
||||
maxTime: number;
|
||||
minTime: number;
|
||||
@ -15,59 +13,57 @@ export interface updateTimeIntervalAction {
|
||||
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
|
||||
|
||||
switch (interval) {
|
||||
case '15min':
|
||||
|
||||
case "15min":
|
||||
maxTime = Date.now() * 1000000; // in nano sec
|
||||
minTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
||||
break;
|
||||
|
||||
case '30min':
|
||||
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
|
||||
minTime = (Date.now() - 1 * 60 * 60 * 1000) * 1000000;
|
||||
break;
|
||||
|
||||
case '6hr':
|
||||
case "6hr":
|
||||
maxTime = Date.now() * 1000000; // in nano sec
|
||||
minTime = (Date.now() - 6 * 60 * 60 * 1000) * 1000000;
|
||||
break;
|
||||
|
||||
case '1day':
|
||||
case "1day":
|
||||
maxTime = Date.now() * 1000000; // in nano sec
|
||||
minTime = (Date.now() - 24 * 60 * 60 * 1000) * 1000000;
|
||||
break;
|
||||
|
||||
case '1week':
|
||||
case "1week":
|
||||
maxTime = Date.now() * 1000000; // in nano sec
|
||||
minTime = (Date.now() - 7 * 24 * 60 * 60 * 1000) * 1000000;
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
if (datetimeRange !== undefined)
|
||||
{
|
||||
case "custom":
|
||||
if (datetimeRange !== undefined) {
|
||||
maxTime = datetimeRange[1] * 1000000; // in nano sec
|
||||
minTime = datetimeRange[0] * 1000000; // in nano sec
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('not found matching case')
|
||||
|
||||
console.log("not found matching case");
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
type: ActionTypes.updateTimeInterval,
|
||||
payload: { maxTime: maxTime, minTime: minTime },
|
||||
|
@ -1,6 +1,6 @@
|
||||
export * from './types';
|
||||
export * from './traceFilters';
|
||||
export * from './traces';
|
||||
export * from './metrics';
|
||||
export * from './usage';
|
||||
export * from './global';
|
||||
export * from "./types";
|
||||
export * from "./traceFilters";
|
||||
export * from "./traces";
|
||||
export * from "./metrics";
|
||||
export * from "./usage";
|
||||
export * from "./global";
|
||||
|
@ -1,42 +1,41 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import metricsAPI from '../api/metricsAPI';
|
||||
import { GlobalTime } from './global';
|
||||
import { ActionTypes } from './types';
|
||||
import { Dispatch } from "redux";
|
||||
import metricsAPI from "../api/metricsAPI";
|
||||
import { GlobalTime } from "./global";
|
||||
import { ActionTypes } from "./types";
|
||||
|
||||
export interface servicesListItem {
|
||||
"serviceName": string;
|
||||
"p99": number;
|
||||
"avgDuration": number;
|
||||
"numCalls": number;
|
||||
"callRate": number;
|
||||
"numErrors": number;
|
||||
"errorRate": number;
|
||||
};
|
||||
serviceName: string;
|
||||
p99: number;
|
||||
avgDuration: number;
|
||||
numCalls: number;
|
||||
callRate: number;
|
||||
numErrors: number;
|
||||
errorRate: number;
|
||||
}
|
||||
|
||||
export interface metricItem {
|
||||
"timestamp":number;
|
||||
"p50":number;
|
||||
"p90":number;
|
||||
"p99":number;
|
||||
"numCalls":number;
|
||||
"callRate":number;
|
||||
"numErrors":number;
|
||||
"errorRate":number;
|
||||
timestamp: number;
|
||||
p50: number;
|
||||
p90: number;
|
||||
p99: number;
|
||||
numCalls: number;
|
||||
callRate: number;
|
||||
numErrors: number;
|
||||
errorRate: number;
|
||||
}
|
||||
|
||||
export interface topEndpointListItem {
|
||||
"p50": number;
|
||||
"p90": number;
|
||||
"p99": number;
|
||||
"numCalls": number;
|
||||
"name": string;
|
||||
};
|
||||
p50: number;
|
||||
p90: number;
|
||||
p99: number;
|
||||
numCalls: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface customMetricsItem {
|
||||
"timestamp": number;
|
||||
"value": number;
|
||||
};
|
||||
|
||||
timestamp: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface getServicesListAction {
|
||||
type: ActionTypes.getServicesList;
|
||||
@ -58,54 +57,82 @@ export interface getFilteredTraceMetricsAction{
|
||||
payload: customMetricsItem[];
|
||||
}
|
||||
|
||||
|
||||
export const getServicesList = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'services?start='+globalTime.minTime+'&end='+globalTime.maxTime;
|
||||
let request_string =
|
||||
"services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime;
|
||||
const response = await metricsAPI.get<servicesListItem[]>(request_string);
|
||||
|
||||
dispatch<getServicesListAction>({
|
||||
type: ActionTypes.getServicesList,
|
||||
payload: response.data
|
||||
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 = (
|
||||
serviceName: string,
|
||||
globalTime: GlobalTime,
|
||||
) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'service/overview?service='+serviceName+'&start='+globalTime.minTime+'&end='+globalTime.maxTime+'&step=60';
|
||||
let request_string =
|
||||
"service/overview?service=" +
|
||||
serviceName +
|
||||
"&start=" +
|
||||
globalTime.minTime +
|
||||
"&end=" +
|
||||
globalTime.maxTime +
|
||||
"&step=60";
|
||||
const response = await metricsAPI.get<metricItem[]>(request_string);
|
||||
|
||||
dispatch<getServiceMetricsAction>({
|
||||
type: ActionTypes.getServiceMetrics,
|
||||
payload: response.data
|
||||
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 = (
|
||||
serviceName: string,
|
||||
globalTime: GlobalTime,
|
||||
) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'service/top_endpoints?service='+serviceName+'&start='+globalTime.minTime+'&end='+globalTime.maxTime;
|
||||
let request_string =
|
||||
"service/top_endpoints?service=" +
|
||||
serviceName +
|
||||
"&start=" +
|
||||
globalTime.minTime +
|
||||
"&end=" +
|
||||
globalTime.maxTime;
|
||||
const response = await metricsAPI.get<topEndpointListItem[]>(request_string);
|
||||
|
||||
dispatch<getTopEndpointsAction>({
|
||||
type: ActionTypes.getTopEndpoints,
|
||||
payload: response.data
|
||||
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 = (
|
||||
filter_params: string,
|
||||
globalTime: GlobalTime,
|
||||
) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'spans/aggregates?start='+globalTime.minTime+'&end='+globalTime.maxTime+'&'+filter_params;
|
||||
let request_string =
|
||||
"spans/aggregates?start=" +
|
||||
globalTime.minTime +
|
||||
"&end=" +
|
||||
globalTime.maxTime +
|
||||
"&" +
|
||||
filter_params;
|
||||
const response = await metricsAPI.get<customMetricsItem[]>(request_string);
|
||||
|
||||
dispatch<getFilteredTraceMetricsAction>({
|
||||
type: ActionTypes.getFilteredTraceMetrics,
|
||||
payload: response.data
|
||||
payload: response.data,
|
||||
//PNOTE - response.data in the axios response has the actual API response
|
||||
});
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
// Action creator must have a type and optionally a payload
|
||||
import { ActionTypes } from './types'
|
||||
import { ActionTypes } from "./types";
|
||||
|
||||
export interface TagItem {
|
||||
key: string;
|
||||
value: string;
|
||||
operator: 'equals'|'contains';
|
||||
operator: "equals" | "contains";
|
||||
}
|
||||
|
||||
export interface LatencyValue {
|
||||
@ -21,8 +21,8 @@ export interface TraceFilters{
|
||||
|
||||
//define interface for action. Action creator always returns object of this type
|
||||
export interface updateTraceFiltersAction {
|
||||
type: ActionTypes.updateTraceFilters,
|
||||
payload: TraceFilters,
|
||||
type: ActionTypes.updateTraceFilters;
|
||||
payload: TraceFilters;
|
||||
}
|
||||
|
||||
export const updateTraceFilters = (traceFilters: TraceFilters) => {
|
||||
@ -30,16 +30,14 @@ export const updateTraceFilters = (traceFilters: TraceFilters) => {
|
||||
type: ActionTypes.updateTraceFilters,
|
||||
payload: traceFilters,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export interface updateInputTagAction {
|
||||
type: ActionTypes.updateInput,
|
||||
payload: string,
|
||||
type: ActionTypes.updateInput;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export const updateInputTag = (Input: string) => {
|
||||
|
||||
return {
|
||||
type: ActionTypes.updateInput,
|
||||
payload: Input,
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { ActionTypes } from './types';
|
||||
import tracesAPI from '../api/tracesAPI';
|
||||
import { Dispatch } from 'redux';
|
||||
import { GlobalTime } from './global';
|
||||
|
||||
import { ActionTypes } from "./types";
|
||||
import tracesAPI from "../api/tracesAPI";
|
||||
import { Dispatch } from "redux";
|
||||
import { GlobalTime } from "./global";
|
||||
|
||||
// PNOTE
|
||||
// define trace interface - what it should return
|
||||
@ -10,7 +9,6 @@ import { GlobalTime } from './global';
|
||||
// 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
|
||||
|
||||
|
||||
export interface Tree {
|
||||
name: string;
|
||||
value: number;
|
||||
@ -21,7 +19,6 @@ export interface RefItem{
|
||||
refType: string;
|
||||
traceID: string;
|
||||
spanID: string;
|
||||
|
||||
}
|
||||
|
||||
export interface TraceTagItem {
|
||||
@ -66,7 +63,7 @@ export interface spanItem{
|
||||
export interface traceItem {
|
||||
traceID: string;
|
||||
spans: spanItem[];
|
||||
processes: { [id: string] : ProcessItem; } ;
|
||||
processes: { [id: string]: ProcessItem };
|
||||
warnings: [];
|
||||
}
|
||||
|
||||
@ -78,16 +75,24 @@ export interface traceResponse{
|
||||
error: [];
|
||||
}
|
||||
|
||||
export type span = [number, string, string, string, string, string, string, string|string[], string|string[], string|string[], pushDStree[]];
|
||||
|
||||
|
||||
export type span = [
|
||||
number,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string | string[],
|
||||
string | string[],
|
||||
string | string[],
|
||||
pushDStree[],
|
||||
];
|
||||
|
||||
export interface spanList {
|
||||
|
||||
events: span[];
|
||||
segmentID: string;
|
||||
columns: string[];
|
||||
|
||||
}
|
||||
|
||||
// export interface traceResponseNew{
|
||||
@ -95,14 +100,12 @@ export interface spanList{
|
||||
// }
|
||||
export interface traceResponseNew {
|
||||
[id: string]: spanList;
|
||||
|
||||
}
|
||||
|
||||
export interface spansWSameTraceIDResponse {
|
||||
[id: string]: spanList;
|
||||
}
|
||||
|
||||
|
||||
export interface FetchTracesAction {
|
||||
type: ActionTypes.fetchTraces;
|
||||
payload: traceResponseNew;
|
||||
@ -116,31 +119,35 @@ export interface FetchTraceItemAction {
|
||||
export const fetchTraces = (globalTime: GlobalTime, filter_params: string) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
if (globalTime) {
|
||||
let request_string = 'spans?limit=100&lookback=2d&start='+globalTime.minTime+'&end='+globalTime.maxTime+'&'+filter_params;
|
||||
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
|
||||
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);
|
||||
let request_string = "traces/" + traceID;
|
||||
const response = await tracesAPI.get<spansWSameTraceIDResponse>(
|
||||
request_string,
|
||||
);
|
||||
|
||||
dispatch<FetchTraceItemAction>({
|
||||
type: ActionTypes.fetchTraceItem,
|
||||
payload: response.data
|
||||
payload: response.data,
|
||||
//PNOTE - response.data in the axios response has the actual API response?
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { FetchTracesAction, FetchTraceItemAction } from './traces';
|
||||
import { updateTraceFiltersAction, updateInputTagAction } from './traceFilters';
|
||||
import {getServicesListAction,getServiceMetricsAction, getTopEndpointsAction, getFilteredTraceMetricsAction} from './metrics'
|
||||
import {getUsageDataAction} from './usage'
|
||||
import {updateTimeIntervalAction} from './global';
|
||||
|
||||
|
||||
import { FetchTracesAction, FetchTraceItemAction } from "./traces";
|
||||
import { updateTraceFiltersAction, updateInputTagAction } from "./traceFilters";
|
||||
import {
|
||||
getServicesListAction,
|
||||
getServiceMetricsAction,
|
||||
getTopEndpointsAction,
|
||||
getFilteredTraceMetricsAction,
|
||||
} from "./metrics";
|
||||
import { getUsageDataAction } from "./usage";
|
||||
import { updateTimeIntervalAction } from "./global";
|
||||
|
||||
export enum ActionTypes {
|
||||
updateTraceFilters,
|
||||
@ -19,4 +22,14 @@ export enum ActionTypes {
|
||||
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,12 +1,11 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import metricsAPI from '../api/metricsAPI';
|
||||
import { ActionTypes } from './types';
|
||||
import { GlobalTime } from './global';
|
||||
|
||||
import { Dispatch } from "redux";
|
||||
import metricsAPI from "../api/metricsAPI";
|
||||
import { ActionTypes } from "./types";
|
||||
import { GlobalTime } from "./global";
|
||||
|
||||
export interface usageDataItem {
|
||||
"timestamp":number;
|
||||
"count":number;
|
||||
timestamp: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface getUsageDataAction {
|
||||
@ -16,13 +15,18 @@ export interface getUsageDataAction {
|
||||
|
||||
export const getUsageData = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'usage?start='+globalTime.minTime+'&end='+globalTime.maxTime+'&step=3600&service=driver';
|
||||
let request_string =
|
||||
"usage?start=" +
|
||||
globalTime.minTime +
|
||||
"&end=" +
|
||||
globalTime.maxTime +
|
||||
"&step=3600&service=driver";
|
||||
//Step can only be multiple of 3600
|
||||
const response = await metricsAPI.get<usageDataItem[]>(request_string);
|
||||
|
||||
dispatch<getUsageDataAction>({
|
||||
type: ActionTypes.getUsageData,
|
||||
payload: response.data
|
||||
payload: response.data,
|
||||
//PNOTE - response.data in the axios response has the actual API response
|
||||
});
|
||||
};
|
||||
|
@ -1,9 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
import { ENVIRONMENT } from "../constants/env";
|
||||
|
||||
// No auth for the API
|
||||
export default axios.create({
|
||||
baseURL: 'https://api.signoz.io/api/prom/api/v1',
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
baseURL: `${ENVIRONMENT.baseURL}/api/prom/api/v1`,
|
||||
});
|
||||
|
@ -1,10 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
import { ENVIRONMENT } from "../constants/env";
|
||||
|
||||
export default axios.create({
|
||||
// baseURL: 'http://104.211.113.204:8080/api/v1/',
|
||||
// baseURL: process.env.REACT_APP_QUERY_SERVICE_URL,
|
||||
// console.log('in metrics API', process.env.QUERY_SERVICE_URL)
|
||||
baseURL: '/api/v1/',
|
||||
|
||||
}
|
||||
);
|
||||
baseURL: `${ENVIRONMENT.baseURL}/api/v1/`,
|
||||
});
|
||||
|
@ -1,14 +1,12 @@
|
||||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
import { ENVIRONMENT } from "../constants/env";
|
||||
|
||||
export default axios.create({
|
||||
// baseURL: 'https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/',
|
||||
// baseURL: 'http://104.211.113.204:8080/api/v1/',
|
||||
baseURL: '/api/v1/',
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// baseURL: "/api/v1/",
|
||||
baseURL: `${ENVIRONMENT.baseURL}/api/v1/`,
|
||||
});
|
||||
|
||||
//https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/sendMessage?chat_id=351813222&text=Hello%20there
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
import { ENVIRONMENT } from "../constants/env";
|
||||
//import { format } from 'path';
|
||||
|
||||
export default axios.create({
|
||||
// baseURL: 'http://104.211.113.204:8080/api/v1/' //comment this line and remove this comment before pushing
|
||||
// baseURL: process.env.QUERY_SERVICE_URL,
|
||||
// console.log('in traces API', process.env.QUERY_SERVICE_URL)
|
||||
baseURL: '/api/v1/',
|
||||
|
||||
|
||||
// baseURL: "/api/v1/",
|
||||
baseURL: `${ENVIRONMENT.baseURL}/api/v1/`,
|
||||
});
|
||||
|
||||
|
@ -1,39 +1,42 @@
|
||||
import React, { Suspense, useState } from 'react';
|
||||
import { Layout, Menu, Switch as ToggleButton, Spin, Row, Col } from 'antd';
|
||||
import React, { Suspense, useState } from "react";
|
||||
import { Layout, Menu, Switch as ToggleButton, Spin, Row, Col } from "antd";
|
||||
import { useThemeSwitcher } from "react-css-theme-switcher";
|
||||
|
||||
|
||||
|
||||
import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
||||
import {
|
||||
|
||||
NavLink,
|
||||
BrowserRouter as Router,
|
||||
Route,
|
||||
Switch,
|
||||
} from "react-router-dom";
|
||||
import {
|
||||
LineChartOutlined,
|
||||
BarChartOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
AlignLeftOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
|
||||
|
||||
import DateTimeSelector from './DateTimeSelector';
|
||||
import ShowBreadcrumbs from './ShowBreadcrumbs';
|
||||
} from "@ant-design/icons";
|
||||
|
||||
import DateTimeSelector from "./DateTimeSelector";
|
||||
import ShowBreadcrumbs from "./ShowBreadcrumbs";
|
||||
import styled from "styled-components";
|
||||
|
||||
const { Content, Footer, Sider } = Layout;
|
||||
|
||||
const ServiceMetrics = React.lazy(() => import('./metrics/ServiceMetricsDef'));
|
||||
const ServiceMap = React.lazy(() => import('./servicemap/ServiceMap'));
|
||||
const TraceDetail = React.lazy(() => import('./traces/TraceDetail'));
|
||||
const TraceGraph = React.lazy(() => import ('./traces/TraceGraphDef' ));
|
||||
const UsageExplorer = React.lazy(() => import ('./usage/UsageExplorerDef' ));
|
||||
const ServicesTable = React.lazy(() => import('./metrics/ServicesTableDef'));
|
||||
const ServiceMetrics = React.lazy(() => import("./metrics/ServiceMetricsDef"));
|
||||
const ServiceMap = React.lazy(() => import("./servicemap/ServiceMap"));
|
||||
const TraceDetail = React.lazy(() => import("./traces/TraceDetail"));
|
||||
const TraceGraph = React.lazy(() => import("./traces/TraceGraphDef"));
|
||||
const UsageExplorer = React.lazy(() => import("./usage/UsageExplorerDef"));
|
||||
const ServicesTable = React.lazy(() => import("./metrics/ServicesTableDef"));
|
||||
// const Signup = React.lazy(() => import('./Signup'));
|
||||
|
||||
|
||||
|
||||
//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.
|
||||
|
||||
const ThemeSwitcherWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
`;
|
||||
const App = () => {
|
||||
// state = { collapsed: false, isDarkMode: true };
|
||||
const { switcher, currentTheme, status, themes } = useThemeSwitcher();
|
||||
@ -45,7 +48,6 @@ const App = () => {
|
||||
switcher({ theme: isChecked ? themes.dark : themes.light });
|
||||
};
|
||||
|
||||
|
||||
const onCollapse = (): void => {
|
||||
setCollapsed(!collapsed);
|
||||
};
|
||||
@ -56,32 +58,64 @@ const App = () => {
|
||||
|
||||
return (
|
||||
<Router basename="/">
|
||||
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={160}>
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<Sider
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onCollapse={onCollapse}
|
||||
width={160}
|
||||
>
|
||||
<div className="logo">
|
||||
<ThemeSwitcherWrapper>
|
||||
<ToggleButton checked={isDarkMode} onChange={toggleTheme} />
|
||||
<NavLink to='/'><img src={"signoz.svg"} alt={'SigNoz'} style={{margin: '5%', width: 100, display: !collapsed ? 'block' : 'none'}} /></NavLink>
|
||||
</ThemeSwitcherWrapper>
|
||||
<NavLink to="/">
|
||||
<img
|
||||
src={"/signoz.svg"}
|
||||
alt={"SigNoz"}
|
||||
style={{
|
||||
margin: "5%",
|
||||
width: 100,
|
||||
display: !collapsed ? "block" : "none",
|
||||
}}
|
||||
/>
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline">
|
||||
<Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
|
||||
<Menu.Item key="1" icon={<BarChartOutlined />}>
|
||||
<NavLink to='/application' style={{fontSize: 12, textDecoration: 'none'}}>Metrics</NavLink>
|
||||
<NavLink
|
||||
to="/application"
|
||||
style={{ fontSize: 12, textDecoration: "none" }}
|
||||
>
|
||||
Metrics
|
||||
</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" icon={<AlignLeftOutlined />}>
|
||||
<NavLink to='/traces' style={{fontSize: 12, textDecoration: 'none'}}>Traces</NavLink>
|
||||
<NavLink to="/traces" style={{ fontSize: 12, textDecoration: "none" }}>
|
||||
Traces
|
||||
</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" icon={<DeploymentUnitOutlined />}>
|
||||
<NavLink to='/service-map' style={{fontSize: 12, textDecoration: 'none'}}>Service Map</NavLink>
|
||||
<NavLink
|
||||
to="/service-map"
|
||||
style={{ fontSize: 12, textDecoration: "none" }}
|
||||
>
|
||||
Service Map
|
||||
</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="4" icon={<LineChartOutlined />}>
|
||||
<NavLink to='/usage-explorer' style={{fontSize: 12, textDecoration: 'none'}}>Usage Explorer</NavLink>
|
||||
<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' }}>
|
||||
<Content style={{ margin: "0 16px" }}>
|
||||
<Row>
|
||||
<Col span={20}>
|
||||
<ShowBreadcrumbs />
|
||||
@ -104,19 +138,16 @@ const App = () => {
|
||||
<Route path="/" component={ServicesTable} />
|
||||
<Route path="/application" exact component={ServicesTable} />
|
||||
{/* <Route path="/signup" component={Signup} /> */}
|
||||
|
||||
</Switch>
|
||||
</Suspense>
|
||||
|
||||
|
||||
</Content>
|
||||
<Footer style={{ textAlign: 'center', fontSize: 10 }}>SigNoz Inc. ©2020 </Footer>
|
||||
<Footer style={{ textAlign: "center", fontSize: 10 }}>
|
||||
SigNoz Inc. ©2020{" "}
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Router>
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
|
@ -1,23 +1,21 @@
|
||||
import React,{Suspense, useState} from 'react';
|
||||
import {Spin} from 'antd';
|
||||
|
||||
|
||||
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
|
||||
|
||||
|
||||
const Signup = React.lazy(() => import('./Signup'));
|
||||
const App = React.lazy(() => import('./App'));
|
||||
import React, { Suspense, useState } from "react";
|
||||
import { Spin } from "antd";
|
||||
|
||||
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);
|
||||
|
||||
return (
|
||||
|
||||
<Router basename="/">
|
||||
|
||||
<Suspense fallback={<Spin size="large" />}>
|
||||
<Switch>
|
||||
<Route path="/signup" exact component={Signup} />
|
||||
@ -28,26 +26,21 @@ const AppWrapper = () => {
|
||||
<Route path="/traces/:id" component={App} />
|
||||
<Route path="/usage-explorer" component={App} />
|
||||
|
||||
<Route path="/" exact
|
||||
<Route
|
||||
path="/"
|
||||
exact
|
||||
render={() => {
|
||||
return (
|
||||
localStorage.getItem('isLoggedIn')==='yes' ?
|
||||
<Redirect to="/application" /> :
|
||||
return localStorage.getItem("isLoggedIn") === "yes" ? (
|
||||
<Redirect to="/application" />
|
||||
) : (
|
||||
<Redirect to="/signup" />
|
||||
)
|
||||
);
|
||||
}}
|
||||
|
||||
/>
|
||||
|
||||
|
||||
|
||||
</Switch>
|
||||
</Suspense>
|
||||
|
||||
</Router>
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default AppWrapper;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, DatePicker} from 'antd';
|
||||
import {DateTimeRangeType} from '../actions'
|
||||
import { Moment } from 'moment'
|
||||
import moment from 'moment';
|
||||
import React, { useState } from "react";
|
||||
import { Modal, DatePicker } from "antd";
|
||||
import { DateTimeRangeType } from "../actions";
|
||||
import { Moment } from "moment";
|
||||
import moment from "moment";
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
@ -12,26 +12,26 @@ interface CustomDateTimeModalProps {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
|
||||
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({ //destructuring props
|
||||
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({
|
||||
//destructuring props
|
||||
visible,
|
||||
onCreate,
|
||||
onCancel,
|
||||
}) => {
|
||||
|
||||
// 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 {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -46,9 +46,11 @@ function disabledDate(current:Moment){
|
||||
style={{ position: "absolute", top: 60, right: 40 }}
|
||||
onOk={() => onCreate(customDateTimeRange ? customDateTimeRange : null)}
|
||||
>
|
||||
|
||||
<RangePicker disabledDate={disabledDate} onOk={handleRangePickerOk} showTime />
|
||||
|
||||
<RangePicker
|
||||
disabledDate={disabledDate}
|
||||
onOk={handleRangePickerOk}
|
||||
showTime
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -1,19 +1,18 @@
|
||||
import React, { useState } from 'react';
|
||||
import {Select, Button,Space, Form} from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Select, Button, Space, Form } from "antd";
|
||||
import styled from "styled-components";
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
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 { RouteComponentProps } from "react-router-dom";
|
||||
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 { METRICS_PAGE_QUERY_PARAM } from "../constants/query";
|
||||
import { LOCAL_STORAGE } from "../constants/localStorage";
|
||||
const { Option } = Select;
|
||||
|
||||
const DateTimeWrapper = styled.div`
|
||||
@ -26,49 +25,72 @@ interface DateTimeSelectorProps extends RouteComponentProps<any> {
|
||||
globalTime: GlobalTime;
|
||||
}
|
||||
|
||||
|
||||
const _DateTimeSelector = (props: DateTimeSelectorProps) => {
|
||||
|
||||
const defaultTime = "15min";
|
||||
const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false);
|
||||
const [timeInterval,setTimeInterval]=useState('15min')
|
||||
const [refreshButtonHidden, setRefreshButtonHidden]=useState(false)
|
||||
|
||||
const [timeInterval, setTimeInterval] = useState(defaultTime);
|
||||
const [refreshButtonHidden, setRefreshButtonHidden] = useState(false);
|
||||
const [form_dtselector] = Form.useForm();
|
||||
|
||||
|
||||
const handleOnSelect = (value:string) =>
|
||||
{
|
||||
if (value === 'custom')
|
||||
{
|
||||
setCustomDTPickerVisible(true);
|
||||
useEffect(() => {
|
||||
const timeDurationInLocalStorage = localStorage.getItem(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
);
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const timeInQueryParam = urlParams.get(METRICS_PAGE_QUERY_PARAM.time);
|
||||
if (timeInQueryParam) {
|
||||
setMetricsTime(timeInQueryParam);
|
||||
} else if (timeDurationInLocalStorage) {
|
||||
setMetricsTime(timeDurationInLocalStorage);
|
||||
}
|
||||
else
|
||||
{
|
||||
}, []);
|
||||
|
||||
const setMetricsTime = (value: string) => {
|
||||
props.history.push({
|
||||
search: '?time='+value,
|
||||
}) //pass time in URL query param for all choices except custom in datetime picker
|
||||
search: `?${METRICS_PAGE_QUERY_PARAM.time}=${value}`,
|
||||
}); //pass time in URL query param for all choices except custom in datetime picker
|
||||
props.updateTimeInterval(value);
|
||||
setTimeInterval(value);
|
||||
};
|
||||
|
||||
const handleOnSelect = (value: string) => {
|
||||
if (value === "custom") {
|
||||
setCustomDTPickerVisible(true);
|
||||
} else {
|
||||
setTimeInterval(value);
|
||||
setRefreshButtonHidden(false); // for normal intervals, show refresh button
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//function called on clicking apply in customDateTimeModal
|
||||
const handleOk = (dateTimeRange:DateTimeRangeType) =>
|
||||
{
|
||||
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()])
|
||||
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")) ,})
|
||||
form_dtselector.setFieldsValue({
|
||||
interval:
|
||||
dateTimeRange[0].format("YYYY/MM/DD HH:mm") +
|
||||
"-" +
|
||||
dateTimeRange[1].format("YYYY/MM/DD HH:mm"),
|
||||
});
|
||||
}
|
||||
setCustomDTPickerVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
const timeSinceLastRefresh = () => {
|
||||
let timeDiffSec = Math.round((Date.now() - Math.round(props.globalTime.maxTime/1000000))/1000);
|
||||
let timeDiffSec = Math.round(
|
||||
(Date.now() - Math.round(props.globalTime.maxTime / 1000000)) / 1000,
|
||||
);
|
||||
|
||||
//How will Refresh button get updated? Needs to be periodically updated via timer.
|
||||
// For now, not returning any text here
|
||||
@ -79,37 +101,48 @@ const _DateTimeSelector = (props:DateTimeSelectorProps) => {
|
||||
// else
|
||||
// return Math.round(timeDiffSec/3600).toString()+' hr';
|
||||
return null;
|
||||
};
|
||||
|
||||
}
|
||||
const handleRefresh = () => {
|
||||
window.localStorage.setItem(
|
||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
||||
timeInterval,
|
||||
);
|
||||
setMetricsTime(timeInterval);
|
||||
};
|
||||
|
||||
const handleRefresh = () =>
|
||||
{
|
||||
props.updateTimeInterval(timeInterval);
|
||||
}
|
||||
if (props.location.pathname.startsWith('/usage-explorer')) {
|
||||
const options = [
|
||||
{ value: "custom", label: "Custom" },
|
||||
{ value: "15min", label: "Last 15 min" },
|
||||
{ value: "30min", label: "Last 30 min" },
|
||||
{ value: "1hr", label: "Last 1 hour" },
|
||||
{ value: "6hr", label: "Last 6 hour" },
|
||||
{ value: "1day", label: "Last 1 day" },
|
||||
{ value: "1week", label: "Last 1 week" },
|
||||
];
|
||||
if (props.location.pathname.startsWith("/usage-explorer")) {
|
||||
return null;
|
||||
} else
|
||||
{
|
||||
} 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>
|
||||
<Form
|
||||
form={form_dtselector}
|
||||
layout="inline"
|
||||
initialValues={{ interval: "15min" }}
|
||||
style={{ marginTop: 10, marginBottom: 10 }}
|
||||
>
|
||||
<FormItem></FormItem>
|
||||
<Select onSelect={handleOnSelect} value={timeInterval}>
|
||||
{options.map(({ value, label }) => (
|
||||
<Option value={value}>{label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem hidden={refreshButtonHidden} name='refresh_button'>
|
||||
|
||||
<Button type="primary" onClick={handleRefresh}>Refresh {timeSinceLastRefresh()}</Button>
|
||||
<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>
|
||||
@ -124,17 +157,13 @@ const _DateTimeSelector = (props:DateTimeSelectorProps) => {
|
||||
</DateTimeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
const mapStateToProps = (state: StoreState): { globalTime: GlobalTime } => {
|
||||
|
||||
return { globalTime: state.globalTime };
|
||||
};
|
||||
|
||||
|
||||
export const DateTimeSelector = connect(mapStateToProps, {
|
||||
updateTimeInterval: updateTimeInterval,
|
||||
|
||||
})(_DateTimeSelector);
|
||||
|
||||
export default withRouter(DateTimeSelector);
|
@ -1,44 +1,39 @@
|
||||
import React from 'react';
|
||||
import {Breadcrumb} from 'antd';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import React from "react";
|
||||
import { Breadcrumb } from "antd";
|
||||
import { Link, withRouter } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
|
||||
const BreadCrumbWrapper = styled.div`
|
||||
padding-top: 20px;
|
||||
padding-left: 20px;
|
||||
`;
|
||||
|
||||
|
||||
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
|
||||
'/application': 'Application',
|
||||
'/traces': 'Traces',
|
||||
'/service-map': 'Service Map',
|
||||
'/usage-explorer': 'Usage Explorer',
|
||||
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
|
||||
"/application": "Application",
|
||||
"/traces": "Traces",
|
||||
"/service-map": "Service Map",
|
||||
"/usage-explorer": "Usage Explorer",
|
||||
};
|
||||
|
||||
|
||||
const ShowBreadcrumbs = withRouter(props => {
|
||||
const ShowBreadcrumbs = withRouter((props) => {
|
||||
const { location } = props;
|
||||
const pathSnippets = location.pathname.split('/').filter(i => i);
|
||||
const pathSnippets = location.pathname.split("/").filter((i) => i);
|
||||
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
|
||||
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
|
||||
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>
|
||||
<Link to={url}>{url.split("/").slice(-1)[0]}</Link>
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
} else
|
||||
{
|
||||
} else {
|
||||
return (
|
||||
<Breadcrumb.Item key={url}>
|
||||
<Link to={url}>{breadcrumbNameMap[url]}</Link>
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
const breadcrumbItems = [
|
||||
<Breadcrumb.Item key="home">
|
||||
@ -46,11 +41,9 @@ const ShowBreadcrumbs = withRouter(props => {
|
||||
</Breadcrumb.Item>,
|
||||
].concat(extraBreadcrumbItems);
|
||||
return (
|
||||
|
||||
<BreadCrumbWrapper>
|
||||
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
|
||||
</BreadCrumbWrapper>
|
||||
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,67 +1,69 @@
|
||||
import React, { useState,useRef, Suspense } from 'react';
|
||||
import { Row, Space, Button, Input, Checkbox } from 'antd'
|
||||
import submitForm from '../api/submitForm';
|
||||
import React, { useState, useRef, Suspense } from "react";
|
||||
import { Row, Space, Button, Input, Checkbox } from "antd";
|
||||
import submitForm from "../api/submitForm";
|
||||
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 [state, setState] = useState({ submitted: false });
|
||||
const [formState, setFormState] = useState({
|
||||
firstName: {value:''},
|
||||
companyName: {value:''},
|
||||
email: {value:''},
|
||||
password: {value:'',valid:true},
|
||||
firstName: { value: "" },
|
||||
companyName: { value: "" },
|
||||
email: { value: "" },
|
||||
password: { value: "", valid: true },
|
||||
emailOptIn: { value: true },
|
||||
})
|
||||
const passwordInput = useRef(null)
|
||||
});
|
||||
const passwordInput = useRef(null);
|
||||
// const { createAccount } = useActions(signupLogic)
|
||||
// const { accountLoading } = useValues(signupLogic)
|
||||
// const { plan } = fromParams()
|
||||
|
||||
const updateForm = (name:any, target:any, valueAttr = 'value') => {
|
||||
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] } })
|
||||
}
|
||||
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) => {
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
|
||||
console.log('in handle submit');
|
||||
console.log("in handle submit");
|
||||
|
||||
setState({ ...state, submitted: true })
|
||||
setState({ ...state, submitted: true });
|
||||
|
||||
/* Password has custom validation */
|
||||
if (!formState.password.valid) {
|
||||
// if (passwordInput.current){
|
||||
// passwordInput.current.focus()
|
||||
// }
|
||||
|
||||
// return
|
||||
}
|
||||
const payload = {
|
||||
@ -71,18 +73,16 @@ const Signup = (props:SignUpProps) => {
|
||||
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)
|
||||
|
||||
|
||||
|
||||
// axios.get(`https://jsonplaceholder.typicode.com/users`)
|
||||
// .then(res => {
|
||||
// console.log(res);
|
||||
// console.log(res.data);
|
||||
// })
|
||||
|
||||
let texttolog = JSON.stringify(payload)
|
||||
let texttolog = JSON.stringify(payload);
|
||||
|
||||
// submitForm.get('sendMessage', {
|
||||
// params: {
|
||||
@ -95,44 +95,57 @@ const Signup = (props:SignUpProps) => {
|
||||
// console.log(res.data);
|
||||
// })
|
||||
|
||||
|
||||
submitForm.post('user?email='+texttolog
|
||||
).then(res => {
|
||||
submitForm.post("user?email=" + texttolog).then((res) => {
|
||||
console.log(res);
|
||||
console.log(res.data);
|
||||
})
|
||||
});
|
||||
|
||||
localStorage.setItem('isLoggedIn', 'yes');
|
||||
props.history.push('/application')
|
||||
localStorage.setItem("isLoggedIn", "yes");
|
||||
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' }}>
|
||||
<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>
|
||||
<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' }}>
|
||||
<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 }}
|
||||
style={{ maxHeight: "100%", maxWidth: 300, marginTop: 64 }}
|
||||
alt=""
|
||||
className="main-img"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
margin: '0 32px',
|
||||
flexDirection: 'column',
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
margin: "0 32px",
|
||||
flexDirection: "column",
|
||||
paddingTop: 32,
|
||||
maxWidth: '32rem',
|
||||
maxWidth: "32rem",
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
@ -142,18 +155,22 @@ const Signup = (props:SignUpProps) => {
|
||||
placeholder="mike@netflix.com"
|
||||
type="email"
|
||||
value={formState.email.value}
|
||||
onChange={(e) => updateForm('email', e.target)}
|
||||
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 ${
|
||||
state.submitted && !formState.password.valid ? "errored" : ""
|
||||
}`}
|
||||
>
|
||||
<label htmlFor="signupPassword">Password</label>
|
||||
<Input.Password
|
||||
value={formState.password.value}
|
||||
onChange={(e) => updateForm('password', e.target)}
|
||||
onChange={(e) => updateForm("password", e.target)}
|
||||
required
|
||||
ref={passwordInput}
|
||||
// disabled={accountLoading}
|
||||
@ -163,7 +180,9 @@ const Signup = (props:SignUpProps) => {
|
||||
{/* <PasswordStrength password={formState.password.value} /> */}
|
||||
</Suspense>
|
||||
{!formState.password && (
|
||||
<span className="caption">Your password must have at least 8 characters.</span>
|
||||
<span className="caption">
|
||||
Your password must have at least 8 characters.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -173,7 +192,7 @@ const Signup = (props:SignUpProps) => {
|
||||
placeholder="Mike"
|
||||
autoFocus
|
||||
value={formState.firstName.value}
|
||||
onChange={(e) => updateForm('firstName', e.target)}
|
||||
onChange={(e) => updateForm("firstName", e.target)}
|
||||
required
|
||||
// disabled={accountLoading}
|
||||
id="signupFirstName"
|
||||
@ -185,7 +204,7 @@ const Signup = (props:SignUpProps) => {
|
||||
<Input
|
||||
placeholder="Netflix"
|
||||
value={formState.companyName.value}
|
||||
onChange={(e) => updateForm('companyName', e.target)}
|
||||
onChange={(e) => updateForm("companyName", e.target)}
|
||||
// disabled={accountLoading}
|
||||
id="signupCompanyName"
|
||||
/>
|
||||
@ -194,11 +213,11 @@ const Signup = (props:SignUpProps) => {
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={formState.emailOptIn.value}
|
||||
onChange={(e) => updateForm('emailOptIn', e.target, 'checked')}
|
||||
onChange={(e) => updateForm("emailOptIn", e.target, "checked")}
|
||||
// disabled={accountLoading}
|
||||
>
|
||||
Send me occasional emails about security and product updates. You may unsubscribe at any
|
||||
time.
|
||||
Send me occasional emails about security and product updates. You may
|
||||
unsubscribe at any time.
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="text-center space-top">
|
||||
@ -228,8 +247,7 @@ const Signup = (props:SignUpProps) => {
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default withRouter(Signup);
|
@ -1,19 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import { ChartOptions } from 'chart.js';
|
||||
import React from "react";
|
||||
import { Line as ChartJSLine } from "react-chartjs-2";
|
||||
import { ChartOptions } from "chart.js";
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
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<{
|
||||
ycoordinate: number;
|
||||
xcoordinate: number;
|
||||
}>`
|
||||
background-color: white;
|
||||
border: 1px solid rgba(219, 112, 147, 0.5);
|
||||
zIndex:10;
|
||||
zindex: 10;
|
||||
position: absolute;
|
||||
top:${props => props.ycoordinate}px;
|
||||
left:${props => props.xcoordinate}px;
|
||||
top: ${(props) => props.ycoordinate}px;
|
||||
left: ${(props) => props.xcoordinate}px;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
`;
|
||||
@ -28,67 +31,58 @@ padding-right:4px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
// PNOTE - Check if this should be the case
|
||||
const theme = 'dark';
|
||||
const theme = "dark";
|
||||
|
||||
interface ErrorRateChartProps extends RouteComponentProps<any> {
|
||||
data : metricItem[],
|
||||
data: metricItem[];
|
||||
}
|
||||
|
||||
interface ErrorRateChart {
|
||||
chartRef: any;
|
||||
}
|
||||
|
||||
|
||||
class ErrorRateChart extends React.Component<ErrorRateChartProps> {
|
||||
|
||||
constructor(props: ErrorRateChartProps) {
|
||||
super(props);
|
||||
this.chartRef = React.createRef();
|
||||
}
|
||||
|
||||
|
||||
|
||||
state = {
|
||||
// data: props.data,
|
||||
xcoordinate: 0,
|
||||
ycoordinate: 0,
|
||||
showpopUp: false,
|
||||
// graphInfo:{}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
onClickhandler = async (e: any, event: any) => {
|
||||
|
||||
var firstPoint;
|
||||
if (this.chartRef) {
|
||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||
}
|
||||
|
||||
if (firstPoint)
|
||||
{// PNOTE - TODO - Is await needed in this expression?
|
||||
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 = () => {
|
||||
this.props.history.push('/traces')
|
||||
}
|
||||
this.props.history.push("/traces");
|
||||
};
|
||||
|
||||
gotoAlertsHandler = () => {
|
||||
this.props.history.push('/service-map')
|
||||
this.props.history.push("/service-map");
|
||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||
}
|
||||
};
|
||||
|
||||
options_charts: ChartOptions = {
|
||||
|
||||
|
||||
onClick: this.onClickhandler,
|
||||
|
||||
maintainAspectRatio: true,
|
||||
@ -96,49 +90,44 @@ class ErrorRateChart extends React.Component<ErrorRateChartProps>{
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Error per sec',
|
||||
text: "Error per sec",
|
||||
fontSize: 20,
|
||||
position:'top',
|
||||
position: "top",
|
||||
padding: 2,
|
||||
fontFamily: 'Arial',
|
||||
fontStyle: 'regular',
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
|
||||
|
||||
fontFamily: "Arial",
|
||||
fontStyle: "regular",
|
||||
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||
},
|
||||
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center',
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
|
||||
labels: {
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||
fontSize: 10,
|
||||
boxWidth: 10,
|
||||
usePointStyle: true,
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
tooltips: {
|
||||
mode: 'label',
|
||||
mode: "label",
|
||||
bodyFontSize: 12,
|
||||
titleFontSize: 12,
|
||||
|
||||
callbacks: {
|
||||
label: function (tooltipItem, data) {
|
||||
|
||||
if (typeof(tooltipItem.yLabel) === 'number')
|
||||
{
|
||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2);
|
||||
if (typeof tooltipItem.yLabel === "number") {
|
||||
return (
|
||||
data.datasets![tooltipItem.datasetIndex!].label +
|
||||
" : " +
|
||||
tooltipItem.yLabel.toFixed(2)
|
||||
);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -165,15 +154,16 @@ class ErrorRateChart extends React.Component<ErrorRateChartProps>{
|
||||
borderDash: [1, 4],
|
||||
color: "#D3D3D3",
|
||||
lineWidth: 0.25,
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
xAxes: [
|
||||
{
|
||||
type: "time",
|
||||
// time: {
|
||||
// unit: 'second'
|
||||
// },
|
||||
distribution:'linear',
|
||||
distribution: "linear",
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
@ -181,60 +171,58 @@ class ErrorRateChart extends React.Component<ErrorRateChartProps>{
|
||||
maxTicksLimit: 10,
|
||||
},
|
||||
// gridLines: false, --> not a valid option
|
||||
}]
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
GraphTracePopUp = () => {
|
||||
|
||||
if (this.state.showpopUp) {
|
||||
return (
|
||||
|
||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
||||
<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;
|
||||
}
|
||||
|
||||
|
||||
);
|
||||
} 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),
|
||||
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
|
||||
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} />
|
||||
<ChartJSLine
|
||||
ref={this.chartRef}
|
||||
data={data_chartJS}
|
||||
options={this.options_charts}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(ErrorRateChart);
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Bar, Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import styled from 'styled-components';
|
||||
import React from "react";
|
||||
import { Bar, Line as ChartJSLine } from "react-chartjs-2";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { customMetricsItem } from '../../actions/metrics'
|
||||
import { customMetricsItem } from "../../actions/metrics";
|
||||
|
||||
const GenVisualizationWrapper = styled.div`
|
||||
height: 160px;
|
||||
@ -14,15 +14,15 @@ interface 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
|
||||
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 options = {
|
||||
@ -32,16 +32,19 @@ const GenericVisualizations = (props: GenericVisualizationsProps) => {
|
||||
display: false,
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
yAxes: [
|
||||
{
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
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
|
||||
@ -52,29 +55,24 @@ const GenericVisualizations = (props: GenericVisualizationsProps) => {
|
||||
maxTicksLimit: 10,
|
||||
},
|
||||
// gridLines: false, --> not a valid option
|
||||
}],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if(props.chartType === 'line')
|
||||
{
|
||||
if (props.chartType === "line") {
|
||||
return (
|
||||
<GenVisualizationWrapper>
|
||||
<ChartJSLine data={data} options={options} />
|
||||
</GenVisualizationWrapper>
|
||||
);
|
||||
|
||||
} else if (props.chartType === 'bar')
|
||||
{
|
||||
} else if (props.chartType === "bar") {
|
||||
return (
|
||||
<GenVisualizationWrapper>
|
||||
<Bar data={data} options={options} />
|
||||
</GenVisualizationWrapper>
|
||||
);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
|
||||
}
|
||||
} else return null;
|
||||
};
|
||||
|
||||
export default GenericVisualizations;
|
@ -1,19 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import { ChartOptions } from 'chart.js';
|
||||
import React from "react";
|
||||
import { Line as ChartJSLine } from "react-chartjs-2";
|
||||
import { ChartOptions } from "chart.js";
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
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<{
|
||||
ycoordinate: number;
|
||||
xcoordinate: number;
|
||||
}>`
|
||||
background-color: white;
|
||||
border: 1px solid rgba(219, 112, 147, 0.5);
|
||||
zIndex:10;
|
||||
zindex: 10;
|
||||
position: absolute;
|
||||
top:${props => props.ycoordinate}px;
|
||||
left:${props => props.xcoordinate}px;
|
||||
top: ${(props) => props.ycoordinate}px;
|
||||
left: ${(props) => props.xcoordinate}px;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
`;
|
||||
@ -28,78 +31,64 @@ padding-right:4px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const theme = 'dark';
|
||||
|
||||
const theme = "dark";
|
||||
|
||||
interface LatencyLineChartProps extends RouteComponentProps<any> {
|
||||
data : metricItem[],
|
||||
popupClickHandler: Function,
|
||||
data: metricItem[];
|
||||
popupClickHandler: Function;
|
||||
}
|
||||
|
||||
interface LatencyLineChart {
|
||||
chartRef: any;
|
||||
}
|
||||
|
||||
|
||||
class LatencyLineChart extends React.Component<LatencyLineChartProps> {
|
||||
|
||||
constructor(props: LatencyLineChartProps) {
|
||||
super(props);
|
||||
this.chartRef = React.createRef();
|
||||
}
|
||||
|
||||
|
||||
|
||||
state = {
|
||||
xcoordinate: 0,
|
||||
ycoordinate: 0,
|
||||
showpopUp: false,
|
||||
firstpoint_ts: 0,
|
||||
// graphInfo:{}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
onClickhandler = async (e: any, event: any) => {
|
||||
|
||||
var firstPoint;
|
||||
if (this.chartRef) {
|
||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||
}
|
||||
|
||||
if (firstPoint)
|
||||
{
|
||||
if (firstPoint) {
|
||||
this.setState({
|
||||
xcoordinate: e.offsetX + 20,
|
||||
ycoordinate: e.offsetY,
|
||||
showpopUp: true,
|
||||
firstpoint_ts: this.props.data[firstPoint._index].timestamp,
|
||||
// graphInfo:{...event}
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
});
|
||||
} 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,
|
||||
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
gotoTracesHandler = (xc: any) => {
|
||||
this.props.history.push('/traces')
|
||||
}
|
||||
this.props.history.push("/traces");
|
||||
};
|
||||
|
||||
gotoAlertsHandler = () => {
|
||||
this.props.history.push('/service-map')
|
||||
this.props.history.push("/service-map");
|
||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||
}
|
||||
};
|
||||
|
||||
options_charts: ChartOptions = {
|
||||
|
||||
onClick: this.onClickhandler,
|
||||
|
||||
maintainAspectRatio: true,
|
||||
@ -107,45 +96,43 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps>{
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Application Latency in ms',
|
||||
text: "Application Latency in ms",
|
||||
fontSize: 20,
|
||||
position:'top',
|
||||
position: "top",
|
||||
padding: 8,
|
||||
fontFamily: 'Arial',
|
||||
fontStyle: 'regular',
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontFamily: "Arial",
|
||||
fontStyle: "regular",
|
||||
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||
},
|
||||
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center',
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
|
||||
labels: {
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||
fontSize: 10,
|
||||
boxWidth: 10,
|
||||
usePointStyle: true,
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
tooltips: {
|
||||
mode: 'label',
|
||||
mode: "label",
|
||||
bodyFontSize: 12,
|
||||
titleFontSize: 12,
|
||||
|
||||
callbacks: {
|
||||
label: function (tooltipItem, data) {
|
||||
|
||||
if (typeof(tooltipItem.yLabel) === 'number')
|
||||
{
|
||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
if (typeof tooltipItem.yLabel === "number") {
|
||||
return (
|
||||
data.datasets![tooltipItem.datasetIndex!].label +
|
||||
" : " +
|
||||
tooltipItem.yLabel.toFixed(2)
|
||||
);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -167,15 +154,16 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps>{
|
||||
borderDash: [1, 4],
|
||||
color: "#D3D3D3",
|
||||
lineWidth: 0.25,
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
xAxes: [
|
||||
{
|
||||
type: "time",
|
||||
// time: {
|
||||
// unit: 'second'
|
||||
// },
|
||||
distribution: 'linear',
|
||||
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: {
|
||||
@ -185,73 +173,76 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps>{
|
||||
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>
|
||||
<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;
|
||||
}
|
||||
|
||||
|
||||
);
|
||||
} 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
|
||||
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
|
||||
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
|
||||
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)',
|
||||
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
|
||||
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)',
|
||||
borderColor: "rgba(57, 255, 20, 1.0)",
|
||||
borderWidth: 2,
|
||||
},
|
||||
]}
|
||||
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.GraphTracePopUp()}
|
||||
<ChartJSLine ref={this.chartRef} data={data_chartJS} options={this.options_charts} />
|
||||
<ChartJSLine
|
||||
ref={this.chartRef}
|
||||
data={data_chartJS}
|
||||
options={this.options_charts}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(LatencyLineChart);
|
@ -1,19 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import { ChartOptions } from 'chart.js';
|
||||
import React from "react";
|
||||
import { Line as ChartJSLine } from "react-chartjs-2";
|
||||
import { ChartOptions } from "chart.js";
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
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<{
|
||||
ycoordinate: number;
|
||||
xcoordinate: number;
|
||||
}>`
|
||||
background-color: white;
|
||||
border: 1px solid rgba(219, 112, 147, 0.5);
|
||||
zIndex:10;
|
||||
zindex: 10;
|
||||
position: absolute;
|
||||
top:${props => props.ycoordinate}px;
|
||||
left:${props => props.xcoordinate}px;
|
||||
top: ${(props) => props.ycoordinate}px;
|
||||
left: ${(props) => props.xcoordinate}px;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
`;
|
||||
@ -28,20 +31,17 @@ padding-right:4px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const theme = 'dark';
|
||||
const theme = "dark";
|
||||
|
||||
interface RequestRateChartProps extends RouteComponentProps<any> {
|
||||
data : metricItem[],
|
||||
data: metricItem[];
|
||||
}
|
||||
|
||||
interface RequestRateChart {
|
||||
chartRef: any;
|
||||
}
|
||||
|
||||
|
||||
class RequestRateChart extends React.Component<RequestRateChartProps> {
|
||||
|
||||
constructor(props: RequestRateChartProps) {
|
||||
super(props);
|
||||
this.chartRef = React.createRef();
|
||||
@ -52,39 +52,35 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
|
||||
ycoordinate: 0,
|
||||
showpopUp: false,
|
||||
// graphInfo:{}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
onClickhandler = async (e: any, event: any) => {
|
||||
|
||||
var firstPoint;
|
||||
if (this.chartRef) {
|
||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||
}
|
||||
|
||||
if (firstPoint)
|
||||
{// PNOTE - TODO - Is await needed in this expression?
|
||||
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 = () => {
|
||||
this.props.history.push('/traces')
|
||||
}
|
||||
this.props.history.push("/traces");
|
||||
};
|
||||
|
||||
gotoAlertsHandler = () => {
|
||||
this.props.history.push('/service-map')
|
||||
this.props.history.push("/service-map");
|
||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||
}
|
||||
};
|
||||
|
||||
options_charts: ChartOptions = {
|
||||
|
||||
onClick: this.onClickhandler,
|
||||
|
||||
maintainAspectRatio: true,
|
||||
@ -92,45 +88,44 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Request per sec',
|
||||
text: "Request per sec",
|
||||
fontSize: 20,
|
||||
position:'top',
|
||||
position: "top",
|
||||
padding: 2,
|
||||
fontFamily: 'Arial',
|
||||
fontStyle: 'regular',
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontFamily: "Arial",
|
||||
fontStyle: "regular",
|
||||
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||
},
|
||||
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center',
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
|
||||
labels: {
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
|
||||
fontSize: 10,
|
||||
boxWidth: 10,
|
||||
usePointStyle: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
tooltips: {
|
||||
mode: 'label',
|
||||
mode: "label",
|
||||
bodyFontSize: 12,
|
||||
titleFontSize: 12,
|
||||
|
||||
callbacks: {
|
||||
label: function (tooltipItem, data) {
|
||||
|
||||
if (typeof(tooltipItem.yLabel) === 'number')
|
||||
{
|
||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2);
|
||||
if (typeof tooltipItem.yLabel === "number") {
|
||||
return (
|
||||
data.datasets![tooltipItem.datasetIndex!].label +
|
||||
" : " +
|
||||
tooltipItem.yLabel.toFixed(2)
|
||||
);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -146,21 +141,21 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
|
||||
maxTicksLimit: 6,
|
||||
},
|
||||
|
||||
|
||||
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',
|
||||
xAxes: [
|
||||
{
|
||||
type: "time",
|
||||
// time: {
|
||||
// unit: 'second'
|
||||
// },
|
||||
distribution:'linear',
|
||||
distribution: "linear",
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
@ -168,56 +163,58 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
|
||||
maxTicksLimit: 10,
|
||||
},
|
||||
// gridLines: false, --> not a valid option
|
||||
}]
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
GraphTracePopUp = () => {
|
||||
|
||||
if (this.state.showpopUp) {
|
||||
return (
|
||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
||||
<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;
|
||||
}
|
||||
|
||||
|
||||
);
|
||||
} 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),
|
||||
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
|
||||
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} />
|
||||
<ChartJSLine
|
||||
ref={this.chartRef}
|
||||
data={data_chartJS}
|
||||
options={this.options_charts}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(RequestRateChart);
|
@ -1,31 +1,36 @@
|
||||
import React,{useEffect} from 'react';
|
||||
import { Tabs, Card, Row, Col} from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useEffect } from "react";
|
||||
import { Tabs, Card, Row, Col } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
import { useParams, RouteComponentProps } from "react-router-dom";
|
||||
import { withRouter } from "react-router";
|
||||
|
||||
import { getServicesMetrics, metricItem, getTopEndpoints, topEndpointListItem, GlobalTime, updateTimeInterval } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import LatencyLineChart from "./LatencyLineChart"
|
||||
import RequestRateChart from './RequestRateChart'
|
||||
import ErrorRateChart from './ErrorRateChart'
|
||||
import TopEndpointsTable from './TopEndpointsTable';
|
||||
import {
|
||||
getServicesMetrics,
|
||||
metricItem,
|
||||
getTopEndpoints,
|
||||
topEndpointListItem,
|
||||
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;
|
||||
|
||||
interface ServicesMetricsProps extends RouteComponentProps<any> {
|
||||
serviceMetrics: metricItem[],
|
||||
getServicesMetrics: Function,
|
||||
topEndpointsList: topEndpointListItem[],
|
||||
getTopEndpoints: Function,
|
||||
globalTime: GlobalTime,
|
||||
updateTimeInterval: Function,
|
||||
serviceMetrics: metricItem[];
|
||||
getServicesMetrics: Function;
|
||||
topEndpointsList: topEndpointListItem[];
|
||||
getTopEndpoints: Function;
|
||||
globalTime: GlobalTime;
|
||||
updateTimeInterval: Function;
|
||||
}
|
||||
|
||||
|
||||
const _ServiceMetrics = (props: ServicesMetricsProps) => {
|
||||
|
||||
const params = useParams<{ servicename?: string; }>();
|
||||
const params = useParams<{ servicename?: string }>();
|
||||
|
||||
useEffect(() => {
|
||||
props.getServicesMetrics(params.servicename, props.globalTime);
|
||||
@ -33,18 +38,22 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
|
||||
}, [props.globalTime, params.servicename]);
|
||||
|
||||
const onTracePopupClick = (timestamp: number) => {
|
||||
|
||||
props.updateTimeInterval('custom',[(timestamp/1000000)-5*60*1000,(timestamp/1000000)])// updateTimeInterval takes second range in ms -- give -5 min to selected time,
|
||||
props.history.push('/traces')
|
||||
}
|
||||
props.updateTimeInterval("custom", [
|
||||
timestamp / 1000000 - 5 * 60 * 1000,
|
||||
timestamp / 1000000,
|
||||
]); // updateTimeInterval takes second range in ms -- give -5 min to selected time,
|
||||
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} />
|
||||
<LatencyLineChart
|
||||
data={props.serviceMetrics}
|
||||
popupClickHandler={onTracePopupClick}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
@ -72,14 +81,15 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
|
||||
|
||||
<TabPane tab="External Calls" key="2">
|
||||
<div style={{ margin: 20 }}> Coming Soon.. </div>
|
||||
<div className="container" style={{ display: 'flex', flexFlow: 'column wrap' }}>
|
||||
|
||||
<div className='row'>
|
||||
<div className='col-md-6 col-sm-12 col-12'>
|
||||
|
||||
<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'>
|
||||
<div className="col-md-6 col-sm-12 col-12">
|
||||
{/* <ChartJSLineChart data={this.state.graphData} /> */}
|
||||
</div>
|
||||
</div>
|
||||
@ -87,17 +97,26 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
|
||||
</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 = (
|
||||
state: StoreState,
|
||||
): {
|
||||
serviceMetrics: metricItem[];
|
||||
topEndpointsList: topEndpointListItem[];
|
||||
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));
|
||||
|
||||
})(_ServiceMetrics),
|
||||
);
|
||||
|
@ -1 +1 @@
|
||||
export { ServiceMetrics as default } from './ServiceMetrics';
|
||||
export { ServiceMetrics as default } from "./ServiceMetrics";
|
||||
|
@ -1,18 +1,17 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Table } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Spin, Table } from "antd";
|
||||
import styled from "styled-components";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
|
||||
import { getServicesList, GlobalTime, servicesListItem } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import { getServicesList, GlobalTime, servicesListItem } from "../../actions";
|
||||
import { StoreState } from "../../reducers";
|
||||
|
||||
interface ServicesTableProps {
|
||||
servicesList: servicesListItem[],
|
||||
getServicesList: Function,
|
||||
globalTime: GlobalTime,
|
||||
servicesList: servicesListItem[];
|
||||
getServicesList: Function;
|
||||
globalTime: GlobalTime;
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@ -20,68 +19,102 @@ padding-top:40px;
|
||||
padding-bottom: 40px;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
.ant-table table { 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; };
|
||||
.ant-table table {
|
||||
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 TableLoadingWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 80px;
|
||||
`
|
||||
|
||||
const LoadingText = styled.div`
|
||||
margin-left: 16px;
|
||||
`
|
||||
|
||||
const columns = [
|
||||
|
||||
{
|
||||
title: 'Application',
|
||||
dataIndex: 'serviceName',
|
||||
key: 'serviceName',
|
||||
render: (text :string) => <NavLink style={{textTransform:'capitalize'}} to={'/application/' + text}><strong>{text}</strong></NavLink>,
|
||||
title: "Application",
|
||||
dataIndex: "serviceName",
|
||||
key: "serviceName",
|
||||
render: (text: string) => (
|
||||
<NavLink style={{ textTransform: "capitalize" }} to={"/application/" + text}>
|
||||
<strong>{text}</strong>
|
||||
</NavLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'P99 latency (in ms)',
|
||||
dataIndex: 'p99',
|
||||
key: 'p99',
|
||||
title: "P99 latency (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: 'Error Rate (in %)',
|
||||
dataIndex: 'errorRate',
|
||||
key: 'errorRate',
|
||||
title: "Error Rate (in %)",
|
||||
dataIndex: "errorRate",
|
||||
key: "errorRate",
|
||||
sorter: (a: any, b: any) => a.errorRate - b.errorRate,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value * 100).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Requests Per Second',
|
||||
dataIndex: 'callRate',
|
||||
key: 'callRate',
|
||||
title: "Requests Per Second",
|
||||
dataIndex: "callRate",
|
||||
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 search = useLocation().search;
|
||||
const time_interval = new URLSearchParams(search).get('time');
|
||||
|
||||
const time_interval = new URLSearchParams(search).get("time");
|
||||
const [dataFetched, setDataFetched] = useState(false)
|
||||
useEffect(() => {
|
||||
props.getServicesList(props.globalTime);
|
||||
/*
|
||||
@Note - Change this from action to thunk
|
||||
*/
|
||||
props.getServicesList(props.globalTime).then(()=>{
|
||||
setDataFetched(true)
|
||||
}).catch((e:string)=>{
|
||||
alert(e)
|
||||
});
|
||||
}, [props.globalTime]);
|
||||
|
||||
|
||||
if(!dataFetched){
|
||||
return (
|
||||
|
||||
<Wrapper>
|
||||
<Table dataSource={props.servicesList} columns={columns} pagination={false} />
|
||||
</Wrapper>
|
||||
|
||||
);
|
||||
|
||||
<TableLoadingWrapper>
|
||||
<Spin/>
|
||||
<LoadingText>Fetching data</LoadingText>
|
||||
</TableLoadingWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { servicesList: servicesListItem[], globalTime: GlobalTime } => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Table
|
||||
dataSource={props.servicesList}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StoreState,
|
||||
): { servicesList: servicesListItem[]; globalTime: GlobalTime } => {
|
||||
return { servicesList: state.servicesList, globalTime: state.globalTime };
|
||||
};
|
||||
|
||||
|
@ -1 +1 @@
|
||||
export { ServicesTable as default } from './ServicesTable';
|
||||
export { ServicesTable as default } from "./ServicesTable";
|
||||
|
@ -1,63 +1,66 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Table } from 'antd'
|
||||
import styled from 'styled-components';
|
||||
import { topEndpointListItem } from '../../actions/metrics';
|
||||
|
||||
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Table } from "antd";
|
||||
import styled from "styled-components";
|
||||
import { topEndpointListItem } from "../../actions/metrics";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
.ant-table table { 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; };
|
||||
.ant-table table {
|
||||
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 {
|
||||
data : topEndpointListItem[],
|
||||
data: topEndpointListItem[];
|
||||
}
|
||||
|
||||
const TopEndpointsTable = (props: TopEndpointsTableProps) => {
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
|
||||
render: (text :string) => <NavLink to={'/' + text}>{text}</NavLink>,
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
|
||||
render: (text: string) => <NavLink to={"/" + text}>{text}</NavLink>,
|
||||
},
|
||||
{
|
||||
title: 'P50 (in ms)',
|
||||
dataIndex: 'p50',
|
||||
key: 'p50',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
title: "Number of Calls",
|
||||
dataIndex: "numCalls",
|
||||
key: "numCalls",
|
||||
sorter: (a: any, b: any) => a.numCalls - b.numCalls,
|
||||
},
|
||||
];
|
||||
@ -67,7 +70,7 @@ const TopEndpointsTable = (props: TopEndpointsTableProps) => {
|
||||
<h6> Top Endpoints</h6>
|
||||
<Table dataSource={props.data} columns={columns} pagination={false} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default TopEndpointsTable;
|
@ -11,7 +11,14 @@ import Graph from "react-graph-vis";
|
||||
// https://github.com/crubier/react-graph-vis/issues/93
|
||||
const graph = {
|
||||
nodes: [
|
||||
{ id: 1, label: "Catalogue", shape: "box", color: "green",border: "black",size: 100 },
|
||||
{
|
||||
id: 1,
|
||||
label: "Catalogue",
|
||||
shape: "box",
|
||||
color: "green",
|
||||
border: "black",
|
||||
size: 100,
|
||||
},
|
||||
{ id: 2, label: "Users", shape: "box", color: "#FFFF00" },
|
||||
{ id: 3, label: "Payment App", shape: "box", color: "#FB7E81" },
|
||||
{ id: 4, label: "My Sql", shape: "box", size: 10, color: "#7BE141" },
|
||||
@ -23,17 +30,17 @@ const graph = {
|
||||
{ from: 1, to: 3, color: { color: "red" } },
|
||||
{ from: 3, to: 4, color: { color: "red" } },
|
||||
{ from: 3, to: 5, color: { color: "red" } },
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
hierarchical: true
|
||||
hierarchical: true,
|
||||
},
|
||||
edges: {
|
||||
color: "#000000"
|
||||
color: "#000000",
|
||||
},
|
||||
height: "500px"
|
||||
height: "500px",
|
||||
};
|
||||
|
||||
// const events = {
|
||||
@ -43,7 +50,6 @@ const graph = {
|
||||
// };
|
||||
|
||||
const ServiceGraph = () => {
|
||||
|
||||
// const [network, setNetwork] = useState(null);
|
||||
|
||||
return (
|
||||
@ -58,14 +64,8 @@ const ServiceGraph = () => {
|
||||
// // if you want access to vis.js network api you can set the state in a parent component using this property
|
||||
// }}
|
||||
/>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default ServiceGraph;
|
||||
|
||||
|
||||
|
@ -1,19 +1,14 @@
|
||||
import React from 'react';
|
||||
import ServiceGraph from './ServiceGraph'
|
||||
import React from "react";
|
||||
import ServiceGraph from "./ServiceGraph";
|
||||
|
||||
const ServiceMap = () => {
|
||||
|
||||
return (
|
||||
|
||||
<div> Service Map module coming soon...
|
||||
<div>
|
||||
{" "}
|
||||
Service Map module coming soon...
|
||||
{/* <ServiceGraph /> */}
|
||||
</div>
|
||||
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default ServiceMap;
|
||||
|
@ -1,77 +1,115 @@
|
||||
import React from 'react';
|
||||
import { Card, Tag } from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import React from "react";
|
||||
import { Card, Tag } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
|
||||
import { StoreState } from '../../reducers'
|
||||
import { TagItem, TraceFilters, updateTraceFilters } from '../../actions';
|
||||
import { StoreState } from "../../reducers";
|
||||
import { TagItem, TraceFilters, updateTraceFilters } from "../../actions";
|
||||
|
||||
interface FilterStateDisplayProps {
|
||||
traceFilters: TraceFilters,
|
||||
updateTraceFilters: Function,
|
||||
|
||||
traceFilters: TraceFilters;
|
||||
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}})
|
||||
|
||||
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)})
|
||||
|
||||
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');}}>
|
||||
<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');}}>
|
||||
</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);}}>
|
||||
</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>))}
|
||||
</Tag>
|
||||
))}
|
||||
</Card>
|
||||
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters } => {
|
||||
return { traceFilters: state.traceFilters };
|
||||
};
|
||||
|
||||
|
||||
export const FilterStateDisplay = connect(mapStateToProps,
|
||||
{
|
||||
export const FilterStateDisplay = connect(mapStateToProps, {
|
||||
updateTraceFilters: updateTraceFilters,
|
||||
|
||||
})(_FilterStateDisplay);
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Modal, Form, InputNumber, Col, Row} from 'antd';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
|
||||
import React from "react";
|
||||
import { Modal, Form, InputNumber, Col, Row } from "antd";
|
||||
import { Store } from "antd/lib/form/interface";
|
||||
|
||||
interface LatencyModalFormProps {
|
||||
visible: boolean;
|
||||
@ -25,12 +24,12 @@ const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
.then((values) => {
|
||||
form.resetFields();
|
||||
onCreate(values); // giving error for values
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('Validate Failed:', info);
|
||||
.catch((info) => {
|
||||
console.log("Validate Failed:", info);
|
||||
});
|
||||
}}
|
||||
>
|
||||
@ -38,7 +37,7 @@ const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ min: '100', max:'500' }}
|
||||
initialValues={{ min: "100", max: "500" }}
|
||||
>
|
||||
<Row>
|
||||
{/* <Input.Group compact> */}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
import {Card,Tabs} from 'antd';
|
||||
import React from "react";
|
||||
import { Card, Tabs } from "antd";
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
|
||||
|
||||
interface spanTagItem {
|
||||
key: string;
|
||||
type: string;
|
||||
@ -11,36 +9,44 @@ interface spanTagItem {
|
||||
}
|
||||
|
||||
interface SelectedSpanDetailsProps {
|
||||
clickedSpanTags: spanTagItem[]
|
||||
clickedSpanTags: spanTagItem[];
|
||||
}
|
||||
|
||||
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>)}
|
||||
{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,95 +1,112 @@
|
||||
import React, {useState,useEffect} from 'react';
|
||||
import GenericVisualizations from '../metrics/GenericVisualization'
|
||||
import {Select, Card, Space, Form} from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import GenericVisualizations from "../metrics/GenericVisualization";
|
||||
import { Select, Card, Space, Form } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { StoreState } from '../../reducers'
|
||||
import {customMetricsItem, getFilteredTraceMetrics, GlobalTime, TraceFilters} from '../../actions';
|
||||
import { StoreState } from "../../reducers";
|
||||
import {
|
||||
customMetricsItem,
|
||||
getFilteredTraceMetrics,
|
||||
GlobalTime,
|
||||
TraceFilters,
|
||||
} from "../../actions";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const entity = [
|
||||
{
|
||||
title: 'Calls',
|
||||
key:'calls',
|
||||
dataindex:'calls'
|
||||
title: "Calls",
|
||||
key: "calls",
|
||||
dataindex: "calls",
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
key:'duration',
|
||||
dataindex:'duration'
|
||||
title: "Duration",
|
||||
key: "duration",
|
||||
dataindex: "duration",
|
||||
},
|
||||
{
|
||||
title: 'Error',
|
||||
key:'error',
|
||||
dataindex:'error'
|
||||
title: "Error",
|
||||
key: "error",
|
||||
dataindex: "error",
|
||||
},
|
||||
{
|
||||
title: 'Status Code',
|
||||
key:'status_code',
|
||||
dataindex:'status_code'
|
||||
title: "Status Code",
|
||||
key: "status_code",
|
||||
dataindex: "status_code",
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
const aggregation_options = [
|
||||
{
|
||||
linked_entity: 'calls',
|
||||
default_selected:{title:'Count', dataindex:'count'},
|
||||
options_available: [ {title:'Count', dataindex:'count'}, {title:'Rate (per sec)', dataindex:'rate_per_sec'}]
|
||||
linked_entity: "calls",
|
||||
default_selected: { title: "Count", dataindex: "count" },
|
||||
options_available: [
|
||||
{ title: "Count", dataindex: "count" },
|
||||
{ title: "Rate (per sec)", dataindex: "rate_per_sec" },
|
||||
],
|
||||
},
|
||||
{
|
||||
linked_entity: 'duration',
|
||||
default_selected:{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'}]
|
||||
options_available: [ {title:'p50', dataindex:'p50'},{title:'p90', dataindex:'p90'}, {title:'p99', dataindex:'p99'}]
|
||||
options_available: [
|
||||
{ title: "p50", dataindex: "p50" },
|
||||
{ title: "p90", dataindex: "p90" },
|
||||
{ title: "p99", dataindex: "p99" },
|
||||
],
|
||||
},
|
||||
{
|
||||
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: "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'}]
|
||||
linked_entity: "status_code",
|
||||
default_selected: { title: "Count", dataindex: "count" },
|
||||
options_available: [{ title: "Count", dataindex: "count" }],
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
interface TraceCustomVisualizationsProps {
|
||||
filteredTraceMetrics: customMetricsItem[],
|
||||
globalTime: GlobalTime,
|
||||
getFilteredTraceMetrics: Function,
|
||||
traceFilters: TraceFilters,
|
||||
filteredTraceMetrics: customMetricsItem[];
|
||||
globalTime: GlobalTime;
|
||||
getFilteredTraceMetrics: Function;
|
||||
traceFilters: TraceFilters;
|
||||
}
|
||||
|
||||
const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
|
||||
|
||||
const [selectedEntity, setSelectedEntity] = useState('calls');
|
||||
const [selectedAggOption, setSelectedAggOption] = useState('count');
|
||||
const [selectedStep, setSelectedStep] = useState('60');
|
||||
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
|
||||
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));
|
||||
request_string =
|
||||
request_string +
|
||||
"&tags=" +
|
||||
encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
||||
if (selectedEntity)
|
||||
request_string=request_string+'&dimension='+selectedEntity.toLowerCase();
|
||||
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)
|
||||
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
|
||||
@ -104,74 +121,68 @@ const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
|
||||
// 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];
|
||||
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']);
|
||||
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') {
|
||||
|
||||
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' }}
|
||||
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) => (
|
||||
{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>
|
||||
|
||||
@ -202,19 +213,25 @@ const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
|
||||
</Space>
|
||||
</Form>
|
||||
|
||||
|
||||
<GenericVisualizations chartType='line' data={props.filteredTraceMetrics}/>
|
||||
<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 };
|
||||
};
|
||||
|
||||
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,
|
||||
|
@ -1,22 +1,16 @@
|
||||
import React from 'react';
|
||||
import {TraceCustomVisualizations} from './TraceCustomVisualizations';
|
||||
import { TraceFilter } from './TraceFilter';
|
||||
import { TraceList } from './TraceList';
|
||||
|
||||
|
||||
|
||||
import React from "react";
|
||||
import { TraceCustomVisualizations } from "./TraceCustomVisualizations";
|
||||
import { TraceFilter } from "./TraceFilter";
|
||||
import { TraceList } from "./TraceList";
|
||||
|
||||
const TraceDetail = () => {
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
<TraceFilter />
|
||||
<TraceCustomVisualizations />
|
||||
<TraceList />
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default TraceDetail;
|
@ -1,18 +1,21 @@
|
||||
import React,{useEffect, useState} from 'react';
|
||||
import { Select, Button, Input, Form, AutoComplete} from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import styled from 'styled-components';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Select, Button, Input, Form, AutoComplete } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
import { Store } from "antd/lib/form/interface";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { updateTraceFilters, fetchTraces, TraceFilters, GlobalTime } from '../../actions';
|
||||
import { StoreState } from '../../reducers';
|
||||
import LatencyModalForm from './LatencyModalForm';
|
||||
import {FilterStateDisplay} from './FilterStateDisplay';
|
||||
|
||||
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
import metricsAPI from '../../api/metricsAPI';
|
||||
import {
|
||||
updateTraceFilters,
|
||||
fetchTraces,
|
||||
TraceFilters,
|
||||
GlobalTime,
|
||||
} from "../../actions";
|
||||
import { StoreState } from "../../reducers";
|
||||
import LatencyModalForm from "./LatencyModalForm";
|
||||
import { FilterStateDisplay } from "./FilterStateDisplay";
|
||||
|
||||
import FormItem from "antd/lib/form/FormItem";
|
||||
import metricsAPI from "../../api/metricsAPI";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -23,74 +26,95 @@ font-size: 12px;
|
||||
`;
|
||||
|
||||
interface TraceFilterProps {
|
||||
traceFilters: TraceFilters,
|
||||
globalTime: GlobalTime,
|
||||
updateTraceFilters: Function,
|
||||
fetchTraces: Function,
|
||||
traceFilters: TraceFilters;
|
||||
globalTime: GlobalTime;
|
||||
updateTraceFilters: Function;
|
||||
fetchTraces: Function;
|
||||
}
|
||||
|
||||
interface TagKeyOptionItem {
|
||||
"tagKeys": string;
|
||||
"tagCount": number;
|
||||
tagKeys: string;
|
||||
tagCount: number;
|
||||
}
|
||||
|
||||
const _TraceFilter = (props: TraceFilterProps) => {
|
||||
|
||||
const [serviceList, setServiceList] = useState<string[]>([]);
|
||||
const [operationList, setOperationsList] = useState<string[]>([]);
|
||||
const [tagKeyOptions, setTagKeyOptions] = useState<TagKeyOptionItem[]>([]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
metricsAPI.get<string[]>('services/list').then(response => {
|
||||
metricsAPI.get<string[]>("services/list").then((response) => {
|
||||
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
|
||||
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));
|
||||
request_string =
|
||||
request_string +
|
||||
"&tags=" +
|
||||
encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
||||
|
||||
props.fetchTraces(props.globalTime, request_string)
|
||||
props.fetchTraces(props.globalTime, request_string);
|
||||
}, [props.traceFilters, props.globalTime]);
|
||||
|
||||
useEffect(() => {
|
||||
let latencyButtonText = "Latency";
|
||||
if (
|
||||
props.traceFilters.latency?.min === "" &&
|
||||
props.traceFilters.latency?.max !== ""
|
||||
)
|
||||
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";
|
||||
|
||||
form_basefilter.setFieldsValue({ latency: latencyButtonText });
|
||||
}, [props.traceFilters.latency]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
let latencyButtonText = 'Latency';
|
||||
if (props.traceFilters.latency?.min === '' && props.traceFilters.latency?.max !== '')
|
||||
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';
|
||||
|
||||
|
||||
form_basefilter.setFieldsValue({latency:latencyButtonText ,})
|
||||
|
||||
}, [props.traceFilters.latency])
|
||||
form_basefilter.setFieldsValue({ service: props.traceFilters.service });
|
||||
}, [props.traceFilters.service]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
form_basefilter.setFieldsValue({service: props.traceFilters.service,})
|
||||
|
||||
}, [props.traceFilters.service])
|
||||
|
||||
useEffect ( () => {
|
||||
|
||||
form_basefilter.setFieldsValue({operation: props.traceFilters.operation,})
|
||||
|
||||
}, [props.traceFilters.operation])
|
||||
form_basefilter.setFieldsValue({ operation: props.traceFilters.operation });
|
||||
}, [props.traceFilters.operation]);
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [loading] = useState(false);
|
||||
|
||||
const [tagKeyValueApplied, setTagKeyValueApplied]=useState(['']);
|
||||
const [latencyFilterValues, setLatencyFilterValues]=useState({min:'',max:''})
|
||||
const [tagKeyValueApplied, setTagKeyValueApplied] = useState([""]);
|
||||
const [latencyFilterValues, setLatencyFilterValues] = useState({
|
||||
min: "",
|
||||
max: "",
|
||||
});
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@ -101,68 +125,88 @@ const _TraceFilter = (props: TraceFilterProps) => {
|
||||
}
|
||||
|
||||
function handleChangeOperation(value: string) {
|
||||
props.updateTraceFilters({...props.traceFilters,operation:value})
|
||||
props.updateTraceFilters({ ...props.traceFilters, operation: value });
|
||||
}
|
||||
|
||||
function handleChangeService(value: string) {
|
||||
let service_request='/service/'+value+'/operations';
|
||||
metricsAPI.get<string[]>(service_request).then(response => {
|
||||
let service_request = "/service/" + value + "/operations";
|
||||
metricsAPI.get<string[]>(service_request).then((response) => {
|
||||
// form_basefilter.resetFields(['operation',])
|
||||
setOperationsList(response.data);
|
||||
});
|
||||
|
||||
let tagkeyoptions_request='tags?service='+value;
|
||||
metricsAPI.get<TagKeyOptionItem[]>(tagkeyoptions_request).then(response => {
|
||||
let tagkeyoptions_request = "tags?service=" + value;
|
||||
metricsAPI.get<TagKeyOptionItem[]>(tagkeyoptions_request).then((response) => {
|
||||
setTagKeyOptions(response.data);
|
||||
});
|
||||
|
||||
props.updateTraceFilters({...props.traceFilters,service:value})
|
||||
|
||||
props.updateTraceFilters({ ...props.traceFilters, service: value });
|
||||
}
|
||||
|
||||
const onLatencyButtonClick = () => {
|
||||
setModalVisible(true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const onLatencyModalApply = (values: Store) => {
|
||||
setModalVisible(false);
|
||||
props.updateTraceFilters({...props.traceFilters,latency:{min:values.min?(parseInt(values.min)*1000000).toString():"", max:values.max?(parseInt(values.max)*1000000).toString():""}})
|
||||
|
||||
}
|
||||
props.updateTraceFilters({
|
||||
...props.traceFilters,
|
||||
latency: {
|
||||
min: values.min ? (parseInt(values.min) * 1000000).toString() : "",
|
||||
max: values.max ? (parseInt(values.max) * 1000000).toString() : "",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onTagFormSubmit = (values: any) => {
|
||||
|
||||
let request_tags= 'service=frontend&tags='+encodeURIComponent(JSON.stringify([{"key":values.tag_key,"value":values.tag_value,"operator":values.operator}]))
|
||||
|
||||
|
||||
if (props.traceFilters.tags){ // If there are existing tag filters present
|
||||
props.updateTraceFilters(
|
||||
let request_tags =
|
||||
"service=frontend&tags=" +
|
||||
encodeURIComponent(
|
||||
JSON.stringify([
|
||||
{
|
||||
key: values.tag_key,
|
||||
value: values.tag_value,
|
||||
operator: values.operator,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
if (props.traceFilters.tags) {
|
||||
// If there are existing tag filters present
|
||||
props.updateTraceFilters({
|
||||
service: props.traceFilters.service,
|
||||
operation: props.traceFilters.operation,
|
||||
latency: props.traceFilters.latency,
|
||||
tags:[...props.traceFilters.tags, {'key':values.tag_key,'value':values.tag_value,'operator':values.operator}]
|
||||
tags: [
|
||||
...props.traceFilters.tags,
|
||||
{
|
||||
key: values.tag_key,
|
||||
value: values.tag_value,
|
||||
operator: values.operator,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
props.updateTraceFilters(
|
||||
{
|
||||
} 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}]
|
||||
tags: [
|
||||
{
|
||||
key: values.tag_key,
|
||||
value: values.tag_value,
|
||||
operator: values.operator,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
form.resetFields();
|
||||
}
|
||||
};
|
||||
|
||||
const onTagClose = (value: string) => {
|
||||
setTagKeyValueApplied(tagKeyValueApplied.filter( e => (e !== value)));
|
||||
|
||||
}
|
||||
setTagKeyValueApplied(tagKeyValueApplied.filter((e) => e !== value));
|
||||
};
|
||||
|
||||
// For autocomplete
|
||||
//Setting value when autocomplete field is changed
|
||||
@ -170,59 +214,104 @@ const _TraceFilter = (props: TraceFilterProps) => {
|
||||
form.setFieldsValue({ tag_key: data });
|
||||
};
|
||||
|
||||
const dataSource = ['status:200'];
|
||||
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>);
|
||||
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;
|
||||
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})
|
||||
}
|
||||
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>)}
|
||||
<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>)}
|
||||
<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 name="latency">
|
||||
<Input
|
||||
style={{ width: 200 }}
|
||||
type="button"
|
||||
onClick={onLatencyButtonClick}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
{/* <FormItem>
|
||||
@ -232,19 +321,23 @@ const _TraceFilter = (props: TraceFilterProps) => {
|
||||
|
||||
<FilterStateDisplay />
|
||||
|
||||
|
||||
{/* // 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'>
|
||||
|
||||
|
||||
<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' }}
|
||||
options={tagKeyOptions.map((s) => {
|
||||
return { value: s.tagKeys };
|
||||
})}
|
||||
style={{ width: 200, textAlign: "center" }}
|
||||
// onSelect={onSelect}
|
||||
// onSearch={onSearch}
|
||||
onChange={onChangeTagKey}
|
||||
@ -255,21 +348,26 @@ const _TraceFilter = (props: TraceFilterProps) => {
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name='operator'>
|
||||
<Select style={{ width: 120, textAlign: 'center' }}>
|
||||
<FormItem name="operator">
|
||||
<Select style={{ width: 120, textAlign: "center" }}>
|
||||
<Option value="equals">EQUAL</Option>
|
||||
<Option value="contains">CONTAINS</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem rules={[{ required: true }]} name='tag_value'>
|
||||
<Input style={{ width: 160, textAlign: 'center',}} placeholder="Tag Value" />
|
||||
<FormItem rules={[{ required: true }]} name="tag_value">
|
||||
<Input
|
||||
style={{ width: 160, textAlign: "center" }}
|
||||
placeholder="Tag Value"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" htmlType="submit"> Apply Tag Filter </Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{" "}
|
||||
Apply Tag Filter{" "}
|
||||
</Button>
|
||||
</FormItem>
|
||||
|
||||
</Form>
|
||||
|
||||
<LatencyModalForm
|
||||
@ -281,9 +379,11 @@ const _TraceFilter = (props: TraceFilterProps) => {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters, globalTime: GlobalTime } => {
|
||||
const mapStateToProps = (
|
||||
state: StoreState,
|
||||
): { traceFilters: TraceFilters; globalTime: GlobalTime } => {
|
||||
return { traceFilters: state.traceFilters, globalTime: state.globalTime };
|
||||
};
|
||||
|
||||
|
@ -1,107 +1,104 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { flamegraph } from 'd3-flame-graph'
|
||||
import { connect } from 'react-redux';
|
||||
import { Card, Button, Row, Col, Space } from 'antd';
|
||||
import * as d3 from 'd3';
|
||||
import * as d3Tip from 'd3-tip';
|
||||
import { flamegraph } from "d3-flame-graph";
|
||||
import { connect } from "react-redux";
|
||||
import { Card, Button, Row, Col, Space } from "antd";
|
||||
import * as d3 from "d3";
|
||||
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
|
||||
|
||||
import './TraceGraph.css'
|
||||
import { spanToTreeUtil } from '../../utils/spanToTree'
|
||||
import { fetchTraceItem , spansWSameTraceIDResponse } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import { TraceGraphColumn } from './TraceGraphColumn'
|
||||
import SelectedSpanDetails from './SelectedSpanDetails'
|
||||
|
||||
import "./TraceGraph.css";
|
||||
import { spanToTreeUtil } from "../../utils/spanToTree";
|
||||
import { fetchTraceItem, spansWSameTraceIDResponse } from "../../actions";
|
||||
import { StoreState } from "../../reducers";
|
||||
import { TraceGraphColumn } from "./TraceGraphColumn";
|
||||
import SelectedSpanDetails from "./SelectedSpanDetails";
|
||||
|
||||
interface TraceGraphProps {
|
||||
|
||||
traceItem: spansWSameTraceIDResponse ,
|
||||
fetchTraceItem: Function,
|
||||
traceItem: spansWSameTraceIDResponse;
|
||||
fetchTraceItem: Function;
|
||||
}
|
||||
|
||||
|
||||
const _TraceGraph = (props: TraceGraphProps) => {
|
||||
|
||||
const params = useParams<{ id?: string; }>();
|
||||
const [clickedSpanTags,setClickedSpanTags]=useState([])
|
||||
const params = useParams<{ id?: string }>();
|
||||
const [clickedSpanTags, setClickedSpanTags] = useState([]);
|
||||
const [resetZoom, setResetZoom] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
//sets span width based on value - which is mapped to duration
|
||||
props.fetchTraceItem(params.id);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.traceItem[0].events.length>0)
|
||||
{
|
||||
if (props.traceItem || resetZoom) {
|
||||
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]);
|
||||
}, [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
|
||||
|
||||
|
||||
|
||||
const tip = d3Tip.default().attr('class', 'd3-tip').html(function(d:any) { return d.data.name+'<br>duration: '+d.data.value});
|
||||
const tip = d3Tip
|
||||
.default()
|
||||
.attr("class", "d3-tip")
|
||||
.html(function (d: any) {
|
||||
return d.data.name + "<br>duration: " + d.data.value;
|
||||
});
|
||||
|
||||
const onClick = (z: any) => {
|
||||
|
||||
setClickedSpanTags(z.data.tags);
|
||||
console.log(`Clicked on ${z.data.name}, id: "${z.id}"`);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const chart = flamegraph()
|
||||
.width(640)
|
||||
.cellHeight(18)
|
||||
.transitionDuration(500)
|
||||
.minFrameSize(5)
|
||||
.sort(true)
|
||||
.inverted(true)
|
||||
.tooltip(tip)
|
||||
.minFrameSize(10)
|
||||
.elided(false)
|
||||
.onClick(onClick)
|
||||
// .title("Trace Flame graph")
|
||||
.differential(false)
|
||||
.selfValue(true); //sets span width based on value - which is mapped to duration
|
||||
|
||||
const resetZoom = () => {
|
||||
chart.resetZoom();
|
||||
}
|
||||
.sort(true)
|
||||
//Use self value=true when we're using not using aggregated option, Which is not our case.
|
||||
// In that case it's doing step function sort of stuff thru computation.
|
||||
// Source flamegraph.js line 557 and 573.
|
||||
// .selfValue(true)
|
||||
.onClick(onClick)
|
||||
.title("Trace Flame graph");
|
||||
|
||||
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, }}>
|
||||
<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={resetZoom}>Reset Zoom</Button>
|
||||
<div id="chart" style={{ fontSize: 12 }}></div>
|
||||
<Button type="primary" onClick={setResetZoom.bind(this, true)}>
|
||||
Reset Zoom
|
||||
</Button>
|
||||
<div id="chart" style={{ fontSize: 12, marginTop: 20}}></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, {
|
||||
fetchTraceItem: fetchTraceItem,
|
||||
|
@ -1,13 +1,12 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Table } from 'antd'
|
||||
|
||||
import { traceResponseNew, pushDStree } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Table } from "antd";
|
||||
|
||||
import { traceResponseNew, pushDStree } from "../../actions";
|
||||
import { StoreState } from "../../reducers";
|
||||
|
||||
interface TraceGraphColumnProps {
|
||||
traces: traceResponseNew,
|
||||
traces: traceResponseNew;
|
||||
}
|
||||
|
||||
interface TableDataSourceItem {
|
||||
@ -17,57 +16,62 @@ interface TableDataSourceItem {
|
||||
duration: number;
|
||||
}
|
||||
|
||||
|
||||
const _TraceGraphColumn = (props: TraceGraphColumnProps) => {
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Start Time (UTC Time)',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
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()
|
||||
|
||||
sortDirections: ["descend", "ascend"],
|
||||
render: (value: number) => new Date(Math.round(value / 1000)).toUTCString(),
|
||||
},
|
||||
{
|
||||
title: 'Duration (in ms)',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
title: "Duration (in ms)",
|
||||
dataIndex: "duration",
|
||||
key: "duration",
|
||||
sorter: (a: any, b: any) => a.duration - b.duration,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
sortDirections: ["descend", "ascend"],
|
||||
render: (value: number) => (value / 1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operationName',
|
||||
key: 'operationName',
|
||||
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()});
|
||||
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,4 +1,4 @@
|
||||
export { TraceGraph as default } from './TraceGraph';
|
||||
export { TraceGraph as default } from "./TraceGraph";
|
||||
|
||||
// PNOTE
|
||||
// Because react.lazy doesn't work on named components
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Table } from 'antd'
|
||||
import React, { useEffect } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Table } from "antd";
|
||||
|
||||
import { traceResponseNew, fetchTraces, pushDStree } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import { traceResponseNew, fetchTraces, pushDStree } from "../../actions";
|
||||
import { StoreState } from "../../reducers";
|
||||
|
||||
interface TraceListProps {
|
||||
traces: traceResponseNew,
|
||||
fetchTraces: Function,
|
||||
traces: traceResponseNew;
|
||||
fetchTraces: Function;
|
||||
}
|
||||
|
||||
interface TableDataSourceItem {
|
||||
@ -20,9 +20,7 @@ interface TableDataSourceItem {
|
||||
duration: number;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
||||
useEffect(() => {
|
||||
@ -42,60 +40,74 @@ const _TraceList = (props: TraceListProps) => {
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Start Time (UTC Time)',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
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))).toUTCString()
|
||||
sortDirections: ["descend", "ascend"],
|
||||
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
|
||||
|
||||
},
|
||||
{
|
||||
title: 'Duration (in ms)',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
title: "Duration (in ms)",
|
||||
dataIndex: "duration",
|
||||
key: "duration",
|
||||
sorter: (a: any, b: any) => a.duration - b.duration,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
sortDirections: ["descend", "ascend"],
|
||||
render: (value: number) => (value / 1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operationName',
|
||||
key: 'operationName',
|
||||
title: "Operation",
|
||||
dataIndex: "operationName",
|
||||
key: "operationName",
|
||||
},
|
||||
{
|
||||
title: 'TraceID',
|
||||
dataIndex: 'traceid',
|
||||
key: 'traceid',
|
||||
render: (text :string) => <NavLink to={'/traces/' + text}>{text.slice(-16)}</NavLink>,
|
||||
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[] = [];
|
||||
|
||||
const renderTraces = () => {
|
||||
|
||||
if (typeof props.traces[0]!== 'undefined' && props.traces[0].events.length > 0) {
|
||||
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
|
||||
|
||||
|
||||
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]), spanid:item[1], traceid:item[2], key:index.toString()});
|
||||
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]),
|
||||
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" />;
|
||||
} else
|
||||
{
|
||||
return <div> No spans found for given filter!</div>
|
||||
} else {
|
||||
return <div> No spans found for given filter!</div>;
|
||||
}
|
||||
|
||||
}; // end of renderTraces
|
||||
|
||||
return (
|
||||
@ -103,9 +115,8 @@ const _TraceList = (props: TraceListProps) => {
|
||||
<div>List of traces with spanID</div>
|
||||
<div>{renderTraces()}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
|
||||
return { traces: state.traces };
|
||||
|
@ -1,37 +1,34 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import { Bar } from 'react-chartjs-2'
|
||||
import { Card } from 'antd'
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useEffect } from "react";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import { Card } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { getUsageData, GlobalTime, usageDataItem } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import { getUsageData, GlobalTime, usageDataItem } from "../../actions";
|
||||
import { StoreState } from "../../reducers";
|
||||
|
||||
interface UsageExplorerProps {
|
||||
usageData: usageDataItem[],
|
||||
getUsageData: Function,
|
||||
globalTime: GlobalTime,
|
||||
usageData: usageDataItem[];
|
||||
getUsageData: Function;
|
||||
globalTime: GlobalTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
props.getUsageData(props.globalTime);
|
||||
}, [props.globalTime]);
|
||||
|
||||
const data = {
|
||||
labels: props.usageData.map(s => new Date(s.timestamp/1000000)),
|
||||
labels: props.usageData.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Span Count',
|
||||
data: props.usageData.map(s => s.count),
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
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 options = {
|
||||
scales: {
|
||||
@ -45,7 +42,7 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
],
|
||||
xAxes: [
|
||||
{
|
||||
type: 'time',
|
||||
type: "time",
|
||||
// distribution: 'linear', // Bar graph doesn't take lineardistribution type?
|
||||
|
||||
ticks: {
|
||||
@ -57,8 +54,8 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@ -66,12 +63,13 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
<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 } => {
|
||||
const mapStateToProps = (
|
||||
state: StoreState,
|
||||
): { usageData: usageDataItem[]; globalTime: GlobalTime } => {
|
||||
return { usageData: state.usageDate, globalTime: state.globalTime };
|
||||
};
|
||||
|
||||
|
@ -1 +1 @@
|
||||
export { UsageExplorer as default } from './UsageExplorer';
|
||||
export { UsageExplorer as default } from "./UsageExplorer";
|
||||
|
4
frontend/src/constants/env.ts
Normal file
4
frontend/src/constants/env.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const ENVIRONMENT = {
|
||||
baseURL: "/api",
|
||||
// baseURL: "http://104.211.113.204:8080",
|
||||
};
|
3
frontend/src/constants/localStorage.ts
Normal file
3
frontend/src/constants/localStorage.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum LOCAL_STORAGE {
|
||||
METRICS_TIME_IN_DURATION = "metricsTimeDuration",
|
||||
}
|
3
frontend/src/constants/query.ts
Normal file
3
frontend/src/constants/query.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum METRICS_PAGE_QUERY_PARAM {
|
||||
time = "time",
|
||||
}
|
@ -1,19 +1,18 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { createStore, applyMiddleware, compose } from "redux";
|
||||
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 AppWrapper from './components/AppWrapper';
|
||||
import './assets/index.css';
|
||||
import { reducers } from './reducers';
|
||||
import AppWrapper from "./components/AppWrapper";
|
||||
import "./assets/index.css";
|
||||
import { reducers } from "./reducers";
|
||||
// import Signup from './components/Signup';
|
||||
|
||||
const store = createStore(reducers, applyMiddleware(thunk))
|
||||
// @ts-ignore
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
|
||||
|
||||
const themes = {
|
||||
dark: `${process.env.PUBLIC_URL}/dark-theme.css`,
|
||||
@ -24,11 +23,10 @@ ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<React.StrictMode>
|
||||
<ThemeSwitcherProvider themeMap={themes} defaultTheme="dark">
|
||||
|
||||
<AppWrapper />
|
||||
{/* <App /> */}
|
||||
</ThemeSwitcherProvider>
|
||||
</React.StrictMode>
|
||||
</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 = (
|
||||
state: GlobalTime = {
|
||||
maxTime: Date.now() * 1000000,
|
||||
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
||||
},
|
||||
action: Action,
|
||||
) => {
|
||||
// Initial global state is time now and 15 minute interval
|
||||
switch (action.type) {
|
||||
|
||||
case ActionTypes.updateTimeInterval:
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
|
||||
}
|
||||
|
||||
};
|
@ -1,22 +1,37 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { traceResponseNew, spansWSameTraceIDResponse, servicesListItem, metricItem, 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'
|
||||
import { combineReducers } from "redux";
|
||||
import {
|
||||
traceResponseNew,
|
||||
spansWSameTraceIDResponse,
|
||||
servicesListItem,
|
||||
metricItem,
|
||||
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 {
|
||||
traceFilters: TraceFilters,
|
||||
inputTag: string,
|
||||
traces: traceResponseNew,
|
||||
traceItem: spansWSameTraceIDResponse ,
|
||||
servicesList: servicesListItem[],
|
||||
serviceMetrics:metricItem[],
|
||||
topEndpointsList:topEndpointListItem[],
|
||||
usageDate:usageDataItem[],
|
||||
globalTime:GlobalTime,
|
||||
filteredTraceMetrics:customMetricsItem[],
|
||||
traceFilters: TraceFilters;
|
||||
inputTag: string;
|
||||
traces: traceResponseNew;
|
||||
traceItem: spansWSameTraceIDResponse;
|
||||
servicesList: servicesListItem[];
|
||||
serviceMetrics: metricItem[];
|
||||
topEndpointsList: topEndpointListItem[];
|
||||
usageDate: usageDataItem[];
|
||||
globalTime: GlobalTime;
|
||||
filteredTraceMetrics: customMetricsItem[];
|
||||
}
|
||||
|
||||
export const reducers = combineReducers<StoreState>({
|
||||
@ -31,4 +46,3 @@ export const reducers = combineReducers<StoreState>({
|
||||
globalTime: updateGlobalTimeReducer,
|
||||
filteredTraceMetrics: filteredTraceMetricsReducer,
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,26 @@
|
||||
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 = (
|
||||
state: servicesListItem[] = [
|
||||
{
|
||||
serviceName: "",
|
||||
p99: 0,
|
||||
avgDuration: 0,
|
||||
numCalls: 0,
|
||||
callRate: 0,
|
||||
numErrors: 0,
|
||||
errorRate: 0,
|
||||
},
|
||||
],
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getServicesList:
|
||||
return action.payload;
|
||||
@ -9,7 +29,21 @@ export const serviceTableReducer = (state: servicesListItem[] = [{"serviceName":
|
||||
}
|
||||
};
|
||||
|
||||
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 = (
|
||||
state: metricItem[] = [
|
||||
{
|
||||
timestamp: 0,
|
||||
p50: 0,
|
||||
p90: 0,
|
||||
p99: 0,
|
||||
numCalls: 0,
|
||||
callRate: 0.0,
|
||||
numErrors: 0,
|
||||
errorRate: 0,
|
||||
},
|
||||
],
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getServiceMetrics:
|
||||
return action.payload;
|
||||
@ -18,7 +52,12 @@ export const serviceTableReducer = (state: servicesListItem[] = [{"serviceName":
|
||||
}
|
||||
};
|
||||
|
||||
export const topEndpointsReducer = (state: topEndpointListItem[] = [{"p50":0,"p90":0,"p99":0,"numCalls":0,"name":''}], action: Action) => {
|
||||
export const topEndpointsReducer = (
|
||||
state: topEndpointListItem[] = [
|
||||
{ p50: 0, p90: 0, p99: 0, numCalls: 0, name: "" },
|
||||
],
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getTopEndpoints:
|
||||
return action.payload;
|
||||
@ -27,8 +66,10 @@ export const serviceTableReducer = (state: servicesListItem[] = [{"serviceName":
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const filteredTraceMetricsReducer = (state: customMetricsItem[] = [{"timestamp": 0, "value": 0}], action: Action) => {
|
||||
export const filteredTraceMetricsReducer = (
|
||||
state: customMetricsItem[] = [{ timestamp: 0, value: 0 }],
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getFilteredTraceMetrics:
|
||||
return action.payload;
|
||||
|
@ -1,26 +1,35 @@
|
||||
import { ActionTypes, TraceFilters, updateInputTagAction, updateTraceFiltersAction } from '../actions';
|
||||
|
||||
export const traceFiltersReducer = (state:TraceFilters = {'service':'', 'tags':[],'operation':'','latency':{'min':'','max':''}}, action: updateTraceFiltersAction) => {
|
||||
import {
|
||||
ActionTypes,
|
||||
TraceFilters,
|
||||
updateInputTagAction,
|
||||
updateTraceFiltersAction,
|
||||
} from "../actions";
|
||||
|
||||
export const traceFiltersReducer = (
|
||||
state: TraceFilters = {
|
||||
service: "",
|
||||
tags: [],
|
||||
operation: "",
|
||||
latency: { min: "", max: "" },
|
||||
},
|
||||
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 = "",
|
||||
action: updateInputTagAction,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.updateInput:
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
@ -1,8 +1,17 @@
|
||||
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
|
||||
var spanlistinstance :spanList ={ events: [], segmentID: '', columns: []} ;
|
||||
export const tracesReducer = (state: traceResponseNew = {"0": spanlistinstance} , action: Action) => {
|
||||
var spanlistinstance: spanList = { events: [], segmentID: "", columns: [] };
|
||||
export const tracesReducer = (
|
||||
state: traceResponseNew = { "0": spanlistinstance },
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.fetchTraces:
|
||||
return action.payload;
|
||||
@ -11,7 +20,10 @@ export const tracesReducer = (state: traceResponseNew = {"0": spanlistinstance}
|
||||
}
|
||||
};
|
||||
|
||||
export const traceItemReducer = (state: spansWSameTraceIDResponse = {"0": spanlistinstance}, action: Action) => {
|
||||
export const traceItemReducer = (
|
||||
state: spansWSameTraceIDResponse = { "0": spanlistinstance },
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.fetchTraceItem:
|
||||
return action.payload;
|
||||
|
@ -1,6 +1,9 @@
|
||||
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 = (
|
||||
state: usageDataItem[] = [{ timestamp: 0, count: 0 }],
|
||||
action: Action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getUsageData:
|
||||
return action.payload;
|
||||
|
@ -1,8 +1,8 @@
|
||||
// dark-theme.less
|
||||
|
||||
@import '~antd/lib/style/color/colorPalette.less';
|
||||
@import '~antd/dist/antd.less';
|
||||
@import '~antd/lib/style/themes/dark.less';
|
||||
@import "~antd/lib/style/color/colorPalette.less";
|
||||
@import "~antd/dist/antd.less";
|
||||
@import "~antd/lib/style/themes/dark.less";
|
||||
|
||||
// @primary-color: #00adb5;
|
||||
// @border-radius-base: 4px;
|
||||
|
@ -1,8 +1,8 @@
|
||||
/* light-theme.less */
|
||||
|
||||
@import '~antd/lib/style/color/colorPalette.less';
|
||||
@import '~antd/dist/antd.less';
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
@import "~antd/lib/style/color/colorPalette.less";
|
||||
@import "~antd/dist/antd.less";
|
||||
@import "~antd/lib/style/themes/default.less";
|
||||
|
||||
/* These are shared variables that can be extracted to their own file */
|
||||
@primary-color: #00adb5;
|
||||
|
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";
|
||||
|
@ -1,2 +1,2 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { createBrowserHistory } from "history";
|
||||
export default createBrowserHistory();
|
@ -1,25 +1,27 @@
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
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 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
|
||||
|
||||
|
||||
let mapped_array : {[id: string] : span;} = {};
|
||||
let mapped_array: { [id: string]: span } = {};
|
||||
|
||||
for (let i = 0; i < spanlist.length; i++) {
|
||||
mapped_array[spanlist[i][1]] = spanlist[i];
|
||||
@ -31,19 +33,16 @@ export const spanToTreeUtil = (spanlist :span[]) :pushDStree => {
|
||||
|
||||
//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]})
|
||||
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] });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,23 +53,26 @@ export const spanToTreeUtil = (spanlist :span[]) :pushDStree => {
|
||||
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)
|
||||
children: mapped_array[id][10],
|
||||
};
|
||||
let referencesArr = mapped_array[id][9];
|
||||
let refArray = [];
|
||||
if (typeof referencesArr === "string") {
|
||||
refArray.push(referencesArr);
|
||||
} else {
|
||||
refArray = referencesArr
|
||||
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("=")
|
||||
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") {
|
||||
@ -87,20 +89,12 @@ export const spanToTreeUtil = (spanlist :span[]) :pushDStree => {
|
||||
if (references[0].refType === "CHILD_OF") {
|
||||
let parentID = references[0].spanID;
|
||||
mapped_array[parentID][10].push(push_object);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
else{
|
||||
} else {
|
||||
tree = push_object;
|
||||
}
|
||||
} // end of for loop
|
||||
|
||||
} // end of if(spans)
|
||||
|
||||
|
||||
|
||||
return(tree)
|
||||
|
||||
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
|
@ -10,11 +10,7 @@
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
@ -22,7 +18,5 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
@ -2672,7 +2672,7 @@ ansi-cyan@^0.1.1:
|
||||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
|
||||
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"
|
||||
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:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2"
|
||||
@ -3958,6 +3963,14 @@ cli-cursor@^3.1.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "3.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.8.0"
|
||||
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"
|
||||
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:
|
||||
version "1.3.0"
|
||||
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:
|
||||
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:
|
||||
version "1.2.0"
|
||||
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"
|
||||
tapable "^1.0.0"
|
||||
|
||||
enquirer@^2.3.5:
|
||||
enquirer@^2.3.5, enquirer@^2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
||||
@ -6067,7 +6097,7 @@ execa@^1.0.0:
|
||||
signal-exit "^3.0.0"
|
||||
strip-eof "^1.0.0"
|
||||
|
||||
execa@^4.0.0:
|
||||
execa@^4.0.0, execa@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
||||
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"
|
||||
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
|
||||
|
||||
figures@^3.0.0:
|
||||
figures@^3.0.0, figures@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
|
||||
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
|
||||
@ -6412,6 +6442,21 @@ find-up@^3.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "2.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.0.4"
|
||||
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"
|
||||
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:
|
||||
version "1.1.0"
|
||||
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:
|
||||
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:
|
||||
version "3.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.7.0"
|
||||
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-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:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
|
||||
@ -9883,6 +10008,13 @@ p-locate@^4.1.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "2.1.0"
|
||||
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:
|
||||
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:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
|
||||
@ -10199,6 +10338,13 @@ pkg-up@3.1.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "0.1.2"
|
||||
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"
|
||||
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:
|
||||
version "5.4.1"
|
||||
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"
|
||||
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
|
||||
|
||||
rxjs@^6.6.0:
|
||||
rxjs@^6.6.0, rxjs@^6.6.3:
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
||||
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
||||
@ -12627,6 +12778,11 @@ selfsigned@^1.10.7:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.1.0"
|
||||
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:
|
||||
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:
|
||||
version "5.7.1"
|
||||
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"
|
||||
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:
|
||||
version "2.1.1"
|
||||
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"
|
||||
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:
|
||||
version "0.2.1"
|
||||
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"
|
||||
xtend "~4.0.1"
|
||||
|
||||
through@^2.3.6:
|
||||
through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
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"
|
||||
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:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
|
Loading…
x
Reference in New Issue
Block a user