Merge pull request #6 from himanshu-source21/main

Fix Sig-13, Fix-11, Logo and lint
This commit is contained in:
Ankit Nayan 2021-01-20 11:48:45 +05:30 committed by GitHub
commit cd16bf43bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 53826 additions and 3350 deletions

4
.gitignore vendored
View File

@ -19,9 +19,13 @@ frontend/.yarnclean
frontend/npm-debug.log* frontend/npm-debug.log*
frontend/yarn-debug.log* frontend/yarn-debug.log*
frontend/yarn-error.log* frontend/yarn-error.log*
frontend/src/constants/env.ts
.idea
**/.vscode **/.vscode
*.tgz *.tgz
**/build **/build
**/storage **/storage
**/locust-scripts/__pycache__/ **/locust-scripts/__pycache__/

View File

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"useTabs": true,
"tabWidth": 1,
"singleQuote": false,
"jsxSingleQuote": false
}

View File

@ -1,19 +1,18 @@
# stage1 as builder # stage1 as builder
FROM node:14-alpine as builder FROM node:14-alpine as builder
WORKDIR /frontend
# copy the package.json to install dependencies # copy the package.json to install dependencies
COPY package.json ./ COPY package.json ./
# Install the dependencies and make the folder # Install the dependencies and make the folder
RUN npm install && mkdir /react-ui && mv ./node_modules ./react-ui RUN yarn install
WORKDIR /react-ui
COPY . . COPY . .
# Build the project and copy the files # Build the project and copy the files
RUN npm run build RUN yarn build
FROM nginx:1.15-alpine 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/* RUN rm -rf /usr/share/nginx/html/*
# Copy from the stahg 1 # 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 EXPOSE 3000

View File

@ -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 # Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).

View File

@ -0,0 +1,7 @@
version: "3.9"
services:
web:
build: .
image: signoz/frontend:latest
ports:
- "3000:3000"

View File

@ -1,21 +1,21 @@
const gulp = require('gulp') const gulp = require("gulp");
const gulpless = require('gulp-less') const gulpless = require("gulp-less");
const postcss = require('gulp-postcss') const postcss = require("gulp-postcss");
const debug = require('gulp-debug') const debug = require("gulp-debug");
var csso = require('gulp-csso') var csso = require("gulp-csso");
const autoprefixer = require('autoprefixer') const autoprefixer = require("autoprefixer");
const NpmImportPlugin = require('less-plugin-npm-import') const NpmImportPlugin = require("less-plugin-npm-import");
gulp.task('less', function () { gulp.task("less", function () {
const plugins = [autoprefixer()] const plugins = [autoprefixer()];
return gulp return gulp
.src('src/themes/*-theme.less') .src("src/themes/*-theme.less")
.pipe(debug({title: 'Less files:'})) .pipe(debug({ title: "Less files:" }))
.pipe( .pipe(
gulpless({ gulpless({
javascriptEnabled: true, javascriptEnabled: true,
plugins: [new NpmImportPlugin({prefix: '~'})], plugins: [new NpmImportPlugin({ prefix: "~" })],
}), }),
) )
.pipe(postcss(plugins)) .pipe(postcss(plugins))
@ -24,5 +24,5 @@ gulp.task('less', function () {
debug: true, debug: true,
}), }),
) )
.pipe(gulp.dest('./public')) .pipe(gulp.dest("./public"));
}) });

View File

@ -51,7 +51,8 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"storybook": "start-storybook -p 6006 -s public --no-dll", "storybook": "start-storybook -p 6006 -s public --no-dll",
"build-storybook": "build-storybook -s public --no-dll" "build-storybook": "build-storybook -s public --no-dll",
"prettify": "prettier --write ."
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@ -81,7 +82,10 @@
"gulp-debug": "^4.0.0", "gulp-debug": "^4.0.0",
"gulp-less": "^4.0.1", "gulp-less": "^4.0.1",
"gulp-postcss": "^9.0.0", "gulp-postcss": "^9.0.0",
"husky": "4.3.8",
"less-plugin-npm-import": "^2.1.0", "less-plugin-npm-import": "^2.1.0",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"react-is": "^17.0.1" "react-is": "^17.0.1"
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -5,12 +5,14 @@
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta name="description" content="Web site created using create-react-app" />
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <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 manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,8 @@
import { ActionTypes } from './types'; import { ActionTypes } from "./types";
import { Moment } from 'moment' import { Moment } from "moment";
export type DateTimeRangeType = [Moment | null, Moment | null] | null; export type DateTimeRangeType = [Moment | null, Moment | null] | null;
export interface GlobalTime { export interface GlobalTime {
maxTime: number; maxTime: number;
minTime: number; minTime: number;
@ -15,59 +13,57 @@ export interface updateTimeIntervalAction {
payload: GlobalTime; payload: GlobalTime;
} }
export const updateTimeInterval = (interval:string, datetimeRange?:[number,number]) => { export const updateTimeInterval = (
interval: string,
datetimeRange?: [number, number],
) => {
let maxTime: number = 0; let maxTime: number = 0;
let minTime: number = 0; let minTime: number = 0;
// if interval string is custom, then datetimRange should be present and max & min time should be // 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 // set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element
switch (interval) { switch (interval) {
case '15min': case "15min":
maxTime = Date.now() * 1000000; // in nano sec maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 15 * 60 * 1000) * 1000000; minTime = (Date.now() - 15 * 60 * 1000) * 1000000;
break; break;
case '30min': case "30min":
maxTime = Date.now() * 1000000; // in nano sec maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 30 * 60 * 1000) * 1000000; minTime = (Date.now() - 30 * 60 * 1000) * 1000000;
break; break;
case '1hr': case "1hr":
maxTime = Date.now() * 1000000; // in nano sec maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 1 * 60 * 60 * 1000) * 1000000; minTime = (Date.now() - 1 * 60 * 60 * 1000) * 1000000;
break; break;
case '6hr': case "6hr":
maxTime = Date.now() * 1000000; // in nano sec maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 6 * 60 * 60 * 1000) * 1000000; minTime = (Date.now() - 6 * 60 * 60 * 1000) * 1000000;
break; break;
case '1day': case "1day":
maxTime = Date.now() * 1000000; // in nano sec maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 24 * 60 * 60 * 1000) * 1000000; minTime = (Date.now() - 24 * 60 * 60 * 1000) * 1000000;
break; break;
case '1week': case "1week":
maxTime = Date.now() * 1000000; // in nano sec maxTime = Date.now() * 1000000; // in nano sec
minTime = (Date.now() - 7 * 24 * 60 * 60 * 1000) * 1000000; minTime = (Date.now() - 7 * 24 * 60 * 60 * 1000) * 1000000;
break; break;
case 'custom': case "custom":
if (datetimeRange !== undefined) if (datetimeRange !== undefined) {
{
maxTime = datetimeRange[1] * 1000000; // in nano sec maxTime = datetimeRange[1] * 1000000; // in nano sec
minTime = datetimeRange[0] * 1000000; // in nano sec minTime = datetimeRange[0] * 1000000; // in nano sec
} }
break; break;
default: default:
console.log('not found matching case') console.log("not found matching case");
} }
return { return {
type: ActionTypes.updateTimeInterval, type: ActionTypes.updateTimeInterval,
payload: { maxTime: maxTime, minTime: minTime }, payload: { maxTime: maxTime, minTime: minTime },

View File

@ -1,6 +1,6 @@
export * from './types'; export * from "./types";
export * from './traceFilters'; export * from "./traceFilters";
export * from './traces'; export * from "./traces";
export * from './metrics'; export * from "./metrics";
export * from './usage'; export * from "./usage";
export * from './global'; export * from "./global";

View File

@ -1,42 +1,41 @@
import { Dispatch } from 'redux'; import { Dispatch } from "redux";
import metricsAPI from '../api/metricsAPI'; import metricsAPI from "../api/metricsAPI";
import { GlobalTime } from './global'; import { GlobalTime } from "./global";
import { ActionTypes } from './types'; import { ActionTypes } from "./types";
export interface servicesListItem { export interface servicesListItem {
"serviceName": string; serviceName: string;
"p99": number; p99: number;
"avgDuration": number; avgDuration: number;
"numCalls": number; numCalls: number;
"callRate": number; callRate: number;
"numErrors": number; numErrors: number;
"errorRate": number; errorRate: number;
}; }
export interface metricItem { export interface metricItem {
"timestamp":number; timestamp: number;
"p50":number; p50: number;
"p90":number; p90: number;
"p99":number; p99: number;
"numCalls":number; numCalls: number;
"callRate":number; callRate: number;
"numErrors":number; numErrors: number;
"errorRate":number; errorRate: number;
} }
export interface topEndpointListItem { export interface topEndpointListItem {
"p50": number; p50: number;
"p90": number; p90: number;
"p99": number; p99: number;
"numCalls": number; numCalls: number;
"name": string; name: string;
}; }
export interface customMetricsItem { export interface customMetricsItem {
"timestamp": number; timestamp: number;
"value": number; value: number;
}; }
export interface getServicesListAction { export interface getServicesListAction {
type: ActionTypes.getServicesList; type: ActionTypes.getServicesList;
@ -58,54 +57,82 @@ export interface getFilteredTraceMetricsAction{
payload: customMetricsItem[]; payload: customMetricsItem[];
} }
export const getServicesList = (globalTime: GlobalTime) => { export const getServicesList = (globalTime: GlobalTime) => {
return async (dispatch: Dispatch) => { return async (dispatch: Dispatch) => {
let request_string = 'services?start='+globalTime.minTime+'&end='+globalTime.maxTime; let request_string =
"services?start=" + globalTime.minTime + "&end=" + globalTime.maxTime;
const response = await metricsAPI.get<servicesListItem[]>(request_string); const response = await metricsAPI.get<servicesListItem[]>(request_string);
dispatch<getServicesListAction>({ dispatch<getServicesListAction>({
type: ActionTypes.getServicesList, type: ActionTypes.getServicesList,
payload: response.data payload: response.data,
//PNOTE - response.data in the axios response has the actual API response //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) => { 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); const response = await metricsAPI.get<metricItem[]>(request_string);
dispatch<getServiceMetricsAction>({ dispatch<getServiceMetricsAction>({
type: ActionTypes.getServiceMetrics, type: ActionTypes.getServiceMetrics,
payload: response.data payload: response.data,
//PNOTE - response.data in the axios response has the actual API response //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) => { 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); const response = await metricsAPI.get<topEndpointListItem[]>(request_string);
dispatch<getTopEndpointsAction>({ dispatch<getTopEndpointsAction>({
type: ActionTypes.getTopEndpoints, type: ActionTypes.getTopEndpoints,
payload: response.data payload: response.data,
//PNOTE - response.data in the axios response has the actual API response //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) => { 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); const response = await metricsAPI.get<customMetricsItem[]>(request_string);
dispatch<getFilteredTraceMetricsAction>({ dispatch<getFilteredTraceMetricsAction>({
type: ActionTypes.getFilteredTraceMetrics, type: ActionTypes.getFilteredTraceMetrics,
payload: response.data payload: response.data,
//PNOTE - response.data in the axios response has the actual API response //PNOTE - response.data in the axios response has the actual API response
}); });
}; };

View File

@ -1,10 +1,10 @@
// Action creator must have a type and optionally a payload // Action creator must have a type and optionally a payload
import { ActionTypes } from './types' import { ActionTypes } from "./types";
export interface TagItem { export interface TagItem {
key: string; key: string;
value: string; value: string;
operator: 'equals'|'contains'; operator: "equals" | "contains";
} }
export interface LatencyValue { export interface LatencyValue {
@ -21,8 +21,8 @@ export interface TraceFilters{
//define interface for action. Action creator always returns object of this type //define interface for action. Action creator always returns object of this type
export interface updateTraceFiltersAction { export interface updateTraceFiltersAction {
type: ActionTypes.updateTraceFilters, type: ActionTypes.updateTraceFilters;
payload: TraceFilters, payload: TraceFilters;
} }
export const updateTraceFilters = (traceFilters: TraceFilters) => { export const updateTraceFilters = (traceFilters: TraceFilters) => {
@ -30,16 +30,14 @@ export const updateTraceFilters = (traceFilters: TraceFilters) => {
type: ActionTypes.updateTraceFilters, type: ActionTypes.updateTraceFilters,
payload: traceFilters, payload: traceFilters,
}; };
}; };
export interface updateInputTagAction { export interface updateInputTagAction {
type: ActionTypes.updateInput, type: ActionTypes.updateInput;
payload: string, payload: string;
} }
export const updateInputTag = (Input: string) => { export const updateInputTag = (Input: string) => {
return { return {
type: ActionTypes.updateInput, type: ActionTypes.updateInput,
payload: Input, payload: Input,

View File

@ -1,8 +1,7 @@
import { ActionTypes } from './types'; import { ActionTypes } from "./types";
import tracesAPI from '../api/tracesAPI'; import tracesAPI from "../api/tracesAPI";
import { Dispatch } from 'redux'; import { Dispatch } from "redux";
import { GlobalTime } from './global'; import { GlobalTime } from "./global";
// PNOTE // PNOTE
// define trace interface - what it should return // define trace interface - what it should return
@ -10,7 +9,6 @@ import { GlobalTime } from './global';
// Date() - takes number of milliseconds as input, our API takes in microseconds // Date() - takes number of milliseconds as input, our API takes in microseconds
// Sample API call for traces - https://api.signoz.io/api/traces?end=1606968273667000&limit=20&lookback=2d&maxDuration=&minDuration=&service=driver&operation=&start=1606968100867000 // Sample API call for traces - https://api.signoz.io/api/traces?end=1606968273667000&limit=20&lookback=2d&maxDuration=&minDuration=&service=driver&operation=&start=1606968100867000
export interface Tree { export interface Tree {
name: string; name: string;
value: number; value: number;
@ -21,7 +19,6 @@ export interface RefItem{
refType: string; refType: string;
traceID: string; traceID: string;
spanID: string; spanID: string;
} }
export interface TraceTagItem { export interface TraceTagItem {
@ -66,7 +63,7 @@ export interface spanItem{
export interface traceItem { export interface traceItem {
traceID: string; traceID: string;
spans: spanItem[]; spans: spanItem[];
processes: { [id: string] : ProcessItem; } ; processes: { [id: string]: ProcessItem };
warnings: []; warnings: [];
} }
@ -78,16 +75,24 @@ export interface traceResponse{
error: []; error: [];
} }
export type span = [number, string, string, string, string, string, string, string|string[], string|string[], string|string[], pushDStree[]]; export type span = [
number,
string,
string,
string,
string,
string,
string,
string | string[],
string | string[],
string | string[],
pushDStree[],
];
export interface spanList { export interface spanList {
events: span[]; events: span[];
segmentID: string; segmentID: string;
columns: string[]; columns: string[];
} }
// export interface traceResponseNew{ // export interface traceResponseNew{
@ -95,14 +100,12 @@ export interface spanList{
// } // }
export interface traceResponseNew { export interface traceResponseNew {
[id: string]: spanList; [id: string]: spanList;
} }
export interface spansWSameTraceIDResponse { export interface spansWSameTraceIDResponse {
[id: string]: spanList; [id: string]: spanList;
} }
export interface FetchTracesAction { export interface FetchTracesAction {
type: ActionTypes.fetchTraces; type: ActionTypes.fetchTraces;
payload: traceResponseNew; payload: traceResponseNew;
@ -116,31 +119,35 @@ export interface FetchTraceItemAction {
export const fetchTraces = (globalTime: GlobalTime, filter_params: string) => { export const fetchTraces = (globalTime: GlobalTime, filter_params: string) => {
return async (dispatch: Dispatch) => { return async (dispatch: Dispatch) => {
if (globalTime) { 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); const response = await tracesAPI.get<traceResponseNew>(request_string);
dispatch<FetchTracesAction>({ dispatch<FetchTracesAction>({
type: ActionTypes.fetchTraces, type: ActionTypes.fetchTraces,
payload: response.data payload: response.data,
//PNOTE - response.data in the axios response has the actual API response? //PNOTE - response.data in the axios response has the actual API response?
}); });
} }
}; };
}; };
export const fetchTraceItem = (traceID: string) => { export const fetchTraceItem = (traceID: string) => {
return async (dispatch: Dispatch) => { return async (dispatch: Dispatch) => {
let request_string = 'traces/'+traceID; let request_string = "traces/" + traceID;
const response = await tracesAPI.get<spansWSameTraceIDResponse>(request_string); const response = await tracesAPI.get<spansWSameTraceIDResponse>(
request_string,
);
dispatch<FetchTraceItemAction>({ dispatch<FetchTraceItemAction>({
type: ActionTypes.fetchTraceItem, type: ActionTypes.fetchTraceItem,
payload: response.data payload: response.data,
//PNOTE - response.data in the axios response has the actual API response? //PNOTE - response.data in the axios response has the actual API response?
}); });
}; };
}; };

View File

@ -1,10 +1,13 @@
import { FetchTracesAction, FetchTraceItemAction } from './traces'; import { FetchTracesAction, FetchTraceItemAction } from "./traces";
import { updateTraceFiltersAction, updateInputTagAction } from './traceFilters'; import { updateTraceFiltersAction, updateInputTagAction } from "./traceFilters";
import {getServicesListAction,getServiceMetricsAction, getTopEndpointsAction, getFilteredTraceMetricsAction} from './metrics' import {
import {getUsageDataAction} from './usage' getServicesListAction,
import {updateTimeIntervalAction} from './global'; getServiceMetricsAction,
getTopEndpointsAction,
getFilteredTraceMetricsAction,
} from "./metrics";
import { getUsageDataAction } from "./usage";
import { updateTimeIntervalAction } from "./global";
export enum ActionTypes { export enum ActionTypes {
updateTraceFilters, updateTraceFilters,
@ -19,4 +22,14 @@ export enum ActionTypes {
getFilteredTraceMetrics, getFilteredTraceMetrics,
} }
export type Action = FetchTraceItemAction | FetchTracesAction | updateTraceFiltersAction | updateInputTagAction |getServicesListAction| getServiceMetricsAction| getTopEndpointsAction | getUsageDataAction | updateTimeIntervalAction| getFilteredTraceMetricsAction; export type Action =
| FetchTraceItemAction
| FetchTracesAction
| updateTraceFiltersAction
| updateInputTagAction
| getServicesListAction
| getServiceMetricsAction
| getTopEndpointsAction
| getUsageDataAction
| updateTimeIntervalAction
| getFilteredTraceMetricsAction;

View File

@ -1,12 +1,11 @@
import { Dispatch } from 'redux'; import { Dispatch } from "redux";
import metricsAPI from '../api/metricsAPI'; import metricsAPI from "../api/metricsAPI";
import { ActionTypes } from './types'; import { ActionTypes } from "./types";
import { GlobalTime } from './global'; import { GlobalTime } from "./global";
export interface usageDataItem { export interface usageDataItem {
"timestamp":number; timestamp: number;
"count":number; count: number;
} }
export interface getUsageDataAction { export interface getUsageDataAction {
@ -16,13 +15,18 @@ export interface getUsageDataAction {
export const getUsageData = (globalTime: GlobalTime) => { export const getUsageData = (globalTime: GlobalTime) => {
return async (dispatch: Dispatch) => { return async (dispatch: Dispatch) => {
let request_string = 'usage?start='+globalTime.minTime+'&end='+globalTime.maxTime+'&step=3600&service=driver'; let request_string =
"usage?start=" +
globalTime.minTime +
"&end=" +
globalTime.maxTime +
"&step=3600&service=driver";
//Step can only be multiple of 3600 //Step can only be multiple of 3600
const response = await metricsAPI.get<usageDataItem[]>(request_string); const response = await metricsAPI.get<usageDataItem[]>(request_string);
dispatch<getUsageDataAction>({ dispatch<getUsageDataAction>({
type: ActionTypes.getUsageData, type: ActionTypes.getUsageData,
payload: response.data payload: response.data,
//PNOTE - response.data in the axios response has the actual API response //PNOTE - response.data in the axios response has the actual API response
}); });
}; };

View File

@ -1,9 +1,7 @@
import axios from 'axios'; import axios from "axios";
import { ENVIRONMENT } from "../constants/env";
// No auth for the API // No auth for the API
export default axios.create({ export default axios.create({
baseURL: 'https://api.signoz.io/api/prom/api/v1', baseURL: `${ENVIRONMENT.baseURL}/api/prom/api/v1`,
} });
);

View File

@ -1,10 +1,6 @@
import axios from 'axios'; import axios from "axios";
import { ENVIRONMENT } from "../constants/env";
export default axios.create({ export default axios.create({
// baseURL: 'http://104.211.113.204:8080/api/v1/', baseURL: `${ENVIRONMENT.baseURL}/api/v1/`,
// baseURL: process.env.REACT_APP_QUERY_SERVICE_URL, });
// console.log('in metrics API', process.env.QUERY_SERVICE_URL)
baseURL: '/api/v1/',
}
);

View File

@ -1,14 +1,12 @@
import axios from 'axios'; import axios from "axios";
import { ENVIRONMENT } from "../constants/env";
export default axios.create({ export default axios.create({
// baseURL: 'https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/', // baseURL: 'https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/',
// baseURL: 'http://104.211.113.204:8080/api/v1/', // baseURL: 'http://104.211.113.204:8080/api/v1/',
baseURL: '/api/v1/', // baseURL: "/api/v1/",
baseURL: `${ENVIRONMENT.baseURL}/api/v1/`,
} });
);
//https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/sendMessage?chat_id=351813222&text=Hello%20there //https://api.telegram.org/bot1518273960:AAHcgVvym9a0Qkl-PKiCI84X1VZaVbkTud0/sendMessage?chat_id=351813222&text=Hello%20there

View File

@ -1,12 +1,11 @@
import axios from 'axios'; import axios from "axios";
import { ENVIRONMENT } from "../constants/env";
//import { format } from 'path'; //import { format } from 'path';
export default axios.create({ export default axios.create({
// baseURL: 'http://104.211.113.204:8080/api/v1/' //comment this line and remove this comment before pushing // baseURL: 'http://104.211.113.204:8080/api/v1/' //comment this line and remove this comment before pushing
// baseURL: process.env.QUERY_SERVICE_URL, // baseURL: process.env.QUERY_SERVICE_URL,
// console.log('in traces API', process.env.QUERY_SERVICE_URL) // console.log('in traces API', process.env.QUERY_SERVICE_URL)
baseURL: '/api/v1/', // baseURL: "/api/v1/",
baseURL: `${ENVIRONMENT.baseURL}/api/v1/`,
}); });

View File

@ -1,39 +1,42 @@
import React, { Suspense, useState } from 'react'; import React, { Suspense, useState } from "react";
import { Layout, Menu, Switch as ToggleButton, Spin, Row, Col } from 'antd'; import { Layout, Menu, Switch as ToggleButton, Spin, Row, Col } from "antd";
import { useThemeSwitcher } from "react-css-theme-switcher"; import { useThemeSwitcher } from "react-css-theme-switcher";
import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { import {
NavLink,
BrowserRouter as Router,
Route,
Switch,
} from "react-router-dom";
import {
LineChartOutlined, LineChartOutlined,
BarChartOutlined, BarChartOutlined,
DeploymentUnitOutlined, DeploymentUnitOutlined,
AlignLeftOutlined, AlignLeftOutlined,
} from '@ant-design/icons'; } from "@ant-design/icons";
import DateTimeSelector from './DateTimeSelector';
import ShowBreadcrumbs from './ShowBreadcrumbs';
import DateTimeSelector from "./DateTimeSelector";
import ShowBreadcrumbs from "./ShowBreadcrumbs";
import styled from "styled-components";
const { Content, Footer, Sider } = Layout; const { Content, Footer, Sider } = Layout;
const ServiceMetrics = React.lazy(() => import('./metrics/ServiceMetricsDef')); const ServiceMetrics = React.lazy(() => import("./metrics/ServiceMetricsDef"));
const ServiceMap = React.lazy(() => import('./servicemap/ServiceMap')); const ServiceMap = React.lazy(() => import("./servicemap/ServiceMap"));
const TraceDetail = React.lazy(() => import('./traces/TraceDetail')); const TraceDetail = React.lazy(() => import("./traces/TraceDetail"));
const TraceGraph = React.lazy(() => import ('./traces/TraceGraphDef' )); const TraceGraph = React.lazy(() => import("./traces/TraceGraphDef"));
const UsageExplorer = React.lazy(() => import ('./usage/UsageExplorerDef' )); const UsageExplorer = React.lazy(() => import("./usage/UsageExplorerDef"));
const ServicesTable = React.lazy(() => import('./metrics/ServicesTableDef')); const ServicesTable = React.lazy(() => import("./metrics/ServicesTableDef"));
// const Signup = React.lazy(() => import('./Signup')); // const Signup = React.lazy(() => import('./Signup'));
//PNOTE //PNOTE
//React. lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don't pull in unused components. //React. lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don't pull in unused components.
const ThemeSwitcherWrapper = styled.div`
display: flex;
justify-content: center;
margin-top: 20px;
`;
const App = () => { const App = () => {
// state = { collapsed: false, isDarkMode: true }; // state = { collapsed: false, isDarkMode: true };
const { switcher, currentTheme, status, themes } = useThemeSwitcher(); const { switcher, currentTheme, status, themes } = useThemeSwitcher();
@ -45,7 +48,6 @@ const App = () => {
switcher({ theme: isChecked ? themes.dark : themes.light }); switcher({ theme: isChecked ? themes.dark : themes.light });
}; };
const onCollapse = (): void => { const onCollapse = (): void => {
setCollapsed(!collapsed); setCollapsed(!collapsed);
}; };
@ -56,32 +58,64 @@ const App = () => {
return ( return (
<Router basename="/"> <Router basename="/">
<Layout style={{ minHeight: "100vh" }}>
<Layout style={{ minHeight: '100vh' }}> <Sider
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={160}> collapsible
collapsed={collapsed}
onCollapse={onCollapse}
width={160}
>
<div className="logo"> <div className="logo">
<ThemeSwitcherWrapper>
<ToggleButton checked={isDarkMode} onChange={toggleTheme} /> <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> </div>
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline"> <Menu theme="dark" defaultSelectedKeys={["1"]} mode="inline">
<Menu.Item key="1" icon={<BarChartOutlined />}> <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>
<Menu.Item key="2" icon={<AlignLeftOutlined />}> <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>
<Menu.Item key="3" icon={<DeploymentUnitOutlined />}> <Menu.Item key="3" icon={<DeploymentUnitOutlined />}>
<NavLink to='/service-map' style={{fontSize: 12, textDecoration: 'none'}}>Service Map</NavLink> <NavLink
to="/service-map"
style={{ fontSize: 12, textDecoration: "none" }}
>
Service Map
</NavLink>
</Menu.Item> </Menu.Item>
<Menu.Item key="4" icon={<LineChartOutlined />}> <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.Item>
</Menu> </Menu>
</Sider> </Sider>
<Layout className="site-layout"> <Layout className="site-layout">
<Content style={{ margin: "0 16px" }}>
<Content style={{ margin: '0 16px' }}>
<Row> <Row>
<Col span={20}> <Col span={20}>
<ShowBreadcrumbs /> <ShowBreadcrumbs />
@ -104,19 +138,16 @@ const App = () => {
<Route path="/" component={ServicesTable} /> <Route path="/" component={ServicesTable} />
<Route path="/application" exact component={ServicesTable} /> <Route path="/application" exact component={ServicesTable} />
{/* <Route path="/signup" component={Signup} /> */} {/* <Route path="/signup" component={Signup} /> */}
</Switch> </Switch>
</Suspense> </Suspense>
</Content> </Content>
<Footer style={{ textAlign: 'center', fontSize: 10 }}>SigNoz Inc. ©2020 </Footer> <Footer style={{ textAlign: "center", fontSize: 10 }}>
SigNoz Inc. ©2020{" "}
</Footer>
</Layout> </Layout>
</Layout> </Layout>
</Router> </Router>
); );
};
}
export default App; export default App;

View File

@ -1,23 +1,21 @@
import React,{Suspense, useState} from 'react'; import React, { Suspense, useState } from "react";
import {Spin} from 'antd'; import { Spin } from "antd";
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
const Signup = React.lazy(() => import('./Signup'));
const App = React.lazy(() => import('./App'));
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 AppWrapper = () => {
const [isUserAuthenticated, setIsUserAuthenticated] = useState(false); const [isUserAuthenticated, setIsUserAuthenticated] = useState(false);
return ( return (
<Router basename="/"> <Router basename="/">
<Suspense fallback={<Spin size="large" />}> <Suspense fallback={<Spin size="large" />}>
<Switch> <Switch>
<Route path="/signup" exact component={Signup} /> <Route path="/signup" exact component={Signup} />
@ -28,26 +26,21 @@ const AppWrapper = () => {
<Route path="/traces/:id" component={App} /> <Route path="/traces/:id" component={App} />
<Route path="/usage-explorer" component={App} /> <Route path="/usage-explorer" component={App} />
<Route path="/" exact <Route
path="/"
exact
render={() => { render={() => {
return ( return localStorage.getItem("isLoggedIn") === "yes" ? (
localStorage.getItem('isLoggedIn')==='yes' ? <Redirect to="/application" />
<Redirect to="/application" /> : ) : (
<Redirect to="/signup" /> <Redirect to="/signup" />
) );
}} }}
/> />
</Switch> </Switch>
</Suspense> </Suspense>
</Router> </Router>
); );
};
}
export default AppWrapper; export default AppWrapper;

View File

@ -1,8 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import { Modal, DatePicker} from 'antd'; import { Modal, DatePicker } from "antd";
import {DateTimeRangeType} from '../actions' import { DateTimeRangeType } from "../actions";
import { Moment } from 'moment' import { Moment } from "moment";
import moment from 'moment'; import moment from "moment";
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
@ -12,26 +12,26 @@ interface CustomDateTimeModalProps {
onCancel: () => void; onCancel: () => void;
} }
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({ //destructuring props //destructuring props
visible, visible,
onCreate, onCreate,
onCancel, onCancel,
}) => { }) => {
// RangeValue<Moment> == [Moment|null,Moment|null]|null // RangeValue<Moment> == [Moment|null,Moment|null]|null
const [customDateTimeRange, setCustomDateTimeRange]=useState<DateTimeRangeType>(); const [
customDateTimeRange,
setCustomDateTimeRange,
] = useState<DateTimeRangeType>();
function handleRangePickerOk(date_time: DateTimeRangeType) { function handleRangePickerOk(date_time: DateTimeRangeType) {
setCustomDateTimeRange(date_time); setCustomDateTimeRange(date_time);
} }
function disabledDate(current: Moment) { function disabledDate(current: Moment) {
if (current > moment()) { if (current > moment()) {
return true; return true;
} } else {
else {
return false; return false;
} }
} }
@ -46,9 +46,11 @@ function disabledDate(current:Moment){
style={{ position: "absolute", top: 60, right: 40 }} style={{ position: "absolute", top: 60, right: 40 }}
onOk={() => onCreate(customDateTimeRange ? customDateTimeRange : null)} onOk={() => onCreate(customDateTimeRange ? customDateTimeRange : null)}
> >
<RangePicker
<RangePicker disabledDate={disabledDate} onOk={handleRangePickerOk} showTime /> disabledDate={disabledDate}
onOk={handleRangePickerOk}
showTime
/>
</Modal> </Modal>
); );
}; };

View File

@ -1,19 +1,18 @@
import React, { useState } from 'react'; import React, { useEffect, useState } from "react";
import {Select, Button,Space, Form} from 'antd'; import { Select, Button, Space, Form } from "antd";
import styled from 'styled-components'; import styled from "styled-components";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from "react-router-dom";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import CustomDateTimeModal from './CustomDateTimeModal';
import { GlobalTime, updateTimeInterval } from '../actions';
import { StoreState } from '../reducers';
import FormItem from 'antd/lib/form/FormItem';
import {DateTimeRangeType} from '../actions'
import CustomDateTimeModal from "./CustomDateTimeModal";
import { GlobalTime, updateTimeInterval } from "../actions";
import { StoreState } from "../reducers";
import FormItem from "antd/lib/form/FormItem";
import { DateTimeRangeType } from "../actions";
import { METRICS_PAGE_QUERY_PARAM } from "../constants/query";
import { LOCAL_STORAGE } from "../constants/localStorage";
const { Option } = Select; const { Option } = Select;
const DateTimeWrapper = styled.div` const DateTimeWrapper = styled.div`
@ -26,49 +25,72 @@ interface DateTimeSelectorProps extends RouteComponentProps<any> {
globalTime: GlobalTime; globalTime: GlobalTime;
} }
const _DateTimeSelector = (props: DateTimeSelectorProps) => { const _DateTimeSelector = (props: DateTimeSelectorProps) => {
const defaultTime = "15min";
const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false); const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false);
const [timeInterval,setTimeInterval]=useState('15min') const [timeInterval, setTimeInterval] = useState(defaultTime);
const [refreshButtonHidden, setRefreshButtonHidden]=useState(false) const [refreshButtonHidden, setRefreshButtonHidden] = useState(false);
const [form_dtselector] = Form.useForm(); const [form_dtselector] = Form.useForm();
useEffect(() => {
const handleOnSelect = (value:string) => const timeDurationInLocalStorage = localStorage.getItem(
{ LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
if (value === 'custom') );
{ const urlParams = new URLSearchParams(window.location.search);
setCustomDTPickerVisible(true); 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({ props.history.push({
search: '?time='+value, search: `?${METRICS_PAGE_QUERY_PARAM.time}=${value}`,
}) //pass time in URL query param for all choices except custom in datetime picker }); //pass time in URL query param for all choices except custom in datetime picker
props.updateTimeInterval(value); props.updateTimeInterval(value);
setTimeInterval(value); setTimeInterval(value);
};
const handleOnSelect = (value: string) => {
if (value === "custom") {
setCustomDTPickerVisible(true);
} else {
setTimeInterval(value);
setRefreshButtonHidden(false); // for normal intervals, show refresh button setRefreshButtonHidden(false); // for normal intervals, show refresh button
} }
} };
//function called on clicking apply in customDateTimeModal //function called on clicking apply in customDateTimeModal
const handleOk = (dateTimeRange:DateTimeRangeType) => const handleOk = (dateTimeRange: DateTimeRangeType) => {
{
// pass values in ms [minTime, maxTime] // pass values in ms [minTime, maxTime]
if (dateTimeRange!== null && dateTimeRange!== undefined && dateTimeRange[0]!== null && dateTimeRange[1]!== null ) if (
{ dateTimeRange !== null &&
props.updateTimeInterval('custom',[dateTimeRange[0].valueOf(),dateTimeRange[1].valueOf()]) dateTimeRange !== undefined &&
dateTimeRange[0] !== null &&
dateTimeRange[1] !== null
) {
props.updateTimeInterval("custom", [
dateTimeRange[0].valueOf(),
dateTimeRange[1].valueOf(),
]);
//setting globaltime //setting globaltime
setRefreshButtonHidden(true); 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); setCustomDTPickerVisible(false);
} };
const timeSinceLastRefresh = () => { 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. //How will Refresh button get updated? Needs to be periodically updated via timer.
// For now, not returning any text here // For now, not returning any text here
@ -79,37 +101,48 @@ const _DateTimeSelector = (props:DateTimeSelectorProps) => {
// else // else
// return Math.round(timeDiffSec/3600).toString()+' hr'; // return Math.round(timeDiffSec/3600).toString()+' hr';
return null; return null;
};
} const handleRefresh = () => {
window.localStorage.setItem(
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
timeInterval,
);
setMetricsTime(timeInterval);
};
const handleRefresh = () => const options = [
{ { value: "custom", label: "Custom" },
props.updateTimeInterval(timeInterval); { value: "15min", label: "Last 15 min" },
} { value: "30min", label: "Last 30 min" },
if (props.location.pathname.startsWith('/usage-explorer')) { { 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; return null;
} else } else {
{
return ( return (
<DateTimeWrapper> <DateTimeWrapper>
<Space> <Space>
<Form form={form_dtselector} layout='inline' initialValues={{ interval:'15min', }} style={{marginTop: 10, marginBottom:10}}> <Form
<FormItem name='interval'> form={form_dtselector}
<Select onSelect={handleOnSelect} > layout="inline"
<Option value="custom">Custom</Option> initialValues={{ interval: "15min" }}
<Option value="15min">Last 15 min</Option> style={{ marginTop: 10, marginBottom: 10 }}
<Option value="30min">Last 30 min</Option> >
<Option value="1hr">Last 1 hour</Option> <FormItem></FormItem>
<Option value="6hr">Last 6 hour</Option> <Select onSelect={handleOnSelect} value={timeInterval}>
<Option value="1day">Last 1 day</Option> {options.map(({ value, label }) => (
<Option value="1week">Last 1 week</Option> <Option value={value}>{label}</Option>
))}
</Select> </Select>
</FormItem>
<FormItem hidden={refreshButtonHidden} name='refresh_button'> <FormItem hidden={refreshButtonHidden} name="refresh_button">
<Button type="primary" onClick={handleRefresh}>
<Button type="primary" onClick={handleRefresh}>Refresh {timeSinceLastRefresh()}</Button> Refresh {timeSinceLastRefresh()}
</Button>
{/* if refresh time is more than x min, give a message? */} {/* if refresh time is more than x min, give a message? */}
</FormItem> </FormItem>
</Form> </Form>
@ -124,17 +157,13 @@ const _DateTimeSelector = (props:DateTimeSelectorProps) => {
</DateTimeWrapper> </DateTimeWrapper>
); );
} }
};
}
const mapStateToProps = (state: StoreState): { globalTime: GlobalTime } => { const mapStateToProps = (state: StoreState): { globalTime: GlobalTime } => {
return { globalTime: state.globalTime }; return { globalTime: state.globalTime };
}; };
export const DateTimeSelector = connect(mapStateToProps, { export const DateTimeSelector = connect(mapStateToProps, {
updateTimeInterval: updateTimeInterval, updateTimeInterval: updateTimeInterval,
})(_DateTimeSelector); })(_DateTimeSelector);
export default withRouter(DateTimeSelector); export default withRouter(DateTimeSelector);

View File

@ -1,44 +1,39 @@
import React from 'react'; import React from "react";
import {Breadcrumb} from 'antd'; import { Breadcrumb } from "antd";
import { Link, withRouter } from 'react-router-dom'; import { Link, withRouter } from "react-router-dom";
import styled from 'styled-components'; import styled from "styled-components";
const BreadCrumbWrapper = styled.div` const BreadCrumbWrapper = styled.div`
padding-top: 20px; padding-top: 20px;
padding-left: 20px; padding-left: 20px;
`; `;
const breadcrumbNameMap: any = {
const breadcrumbNameMap :any = { // PNOTE - TO DO - Remove any and do typechecking - like https://stackoverflow.com/questions/56568423/typescript-no-index-signature-with-a-parameter-of-type-string-was-found-on-ty // PNOTE - TO DO - Remove any and do typechecking - like https://stackoverflow.com/questions/56568423/typescript-no-index-signature-with-a-parameter-of-type-string-was-found-on-ty
'/application': 'Application', "/application": "Application",
'/traces': 'Traces', "/traces": "Traces",
'/service-map': 'Service Map', "/service-map": "Service Map",
'/usage-explorer': 'Usage Explorer', "/usage-explorer": "Usage Explorer",
}; };
const ShowBreadcrumbs = withRouter((props) => {
const ShowBreadcrumbs = withRouter(props => {
const { location } = 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 extraBreadcrumbItems = pathSnippets.map((_, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`; const url = `/${pathSnippets.slice(0, index + 1).join("/")}`;
if (breadcrumbNameMap[url] === undefined) { if (breadcrumbNameMap[url] === undefined) {
return ( return (
<Breadcrumb.Item key={url}> <Breadcrumb.Item key={url}>
<Link to={url}>{url.split('/').slice(-1)[0]}</Link> <Link to={url}>{url.split("/").slice(-1)[0]}</Link>
</Breadcrumb.Item> </Breadcrumb.Item>
); );
} else } else {
{
return ( return (
<Breadcrumb.Item key={url}> <Breadcrumb.Item key={url}>
<Link to={url}>{breadcrumbNameMap[url]}</Link> <Link to={url}>{breadcrumbNameMap[url]}</Link>
</Breadcrumb.Item> </Breadcrumb.Item>
); );
} }
}); });
const breadcrumbItems = [ const breadcrumbItems = [
<Breadcrumb.Item key="home"> <Breadcrumb.Item key="home">
@ -46,11 +41,9 @@ const ShowBreadcrumbs = withRouter(props => {
</Breadcrumb.Item>, </Breadcrumb.Item>,
].concat(extraBreadcrumbItems); ].concat(extraBreadcrumbItems);
return ( return (
<BreadCrumbWrapper> <BreadCrumbWrapper>
<Breadcrumb>{breadcrumbItems}</Breadcrumb> <Breadcrumb>{breadcrumbItems}</Breadcrumb>
</BreadCrumbWrapper> </BreadCrumbWrapper>
); );
}); });

View File

@ -1,67 +1,69 @@
import React, { useState,useRef, Suspense } from 'react'; import React, { useState, useRef, Suspense } from "react";
import { Row, Space, Button, Input, Checkbox } from 'antd' import { Row, Space, Button, Input, Checkbox } from "antd";
import submitForm from '../api/submitForm'; import submitForm from "../api/submitForm";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from "react-router-dom";
interface SignUpProps extends RouteComponentProps<any> {}
interface SignUpProps extends RouteComponentProps<any> {
}
const Signup = (props: SignUpProps) => { const Signup = (props: SignUpProps) => {
const [state, setState] = useState({ submitted: false });
const [state, setState] = useState({ submitted: false })
const [formState, setFormState] = useState({ const [formState, setFormState] = useState({
firstName: {value:''}, firstName: { value: "" },
companyName: {value:''}, companyName: { value: "" },
email: {value:''}, email: { value: "" },
password: {value:'',valid:true}, password: { value: "", valid: true },
emailOptIn: { value: true }, emailOptIn: { value: true },
}) });
const passwordInput = useRef(null) const passwordInput = useRef(null);
// const { createAccount } = useActions(signupLogic) // const { createAccount } = useActions(signupLogic)
// const { accountLoading } = useValues(signupLogic) // const { accountLoading } = useValues(signupLogic)
// const { plan } = fromParams() // const { plan } = fromParams()
const updateForm = (name:any, target:any, valueAttr = 'value') => { const updateForm = (name: any, target: any, valueAttr = "value") => {
/* Validate password (if applicable) */ /* Validate password (if applicable) */
if (name === 'password') { if (name === "password") {
let password = target[valueAttr] let password = target[valueAttr];
const valid = password.length >= 8 const valid = password.length >= 8;
setFormState({ ...formState, password: { ...formState.password, valid, value: target[valueAttr] } }) setFormState({
} else ...formState,
if (name === 'firstName') { password: { ...formState.password, valid, value: target[valueAttr] },
});
setFormState({ ...formState, firstName: { ...formState.firstName, value: target[valueAttr] } }) } else if (name === "firstName") {
} else setFormState({
if (name === 'companyName') { ...formState,
firstName: { ...formState.firstName, value: target[valueAttr] },
setFormState({ ...formState, companyName: { ...formState.companyName, value: target[valueAttr] } }) });
} else } else if (name === "companyName") {
if (name === 'email') { setFormState({
...formState,
setFormState({ ...formState, email: { ...formState.email, value: target[valueAttr] } }) companyName: { ...formState.companyName, value: target[valueAttr] },
} else });
if (name === 'emailOptIn') { } else if (name === "email") {
setFormState({
setFormState({ ...formState, emailOptIn: { ...formState.emailOptIn, value: target[valueAttr] } }) ...formState,
} email: { ...formState.email, value: target[valueAttr] },
});
} else if (name === "emailOptIn") {
setFormState({
...formState,
emailOptIn: { ...formState.emailOptIn, value: target[valueAttr] },
});
} }
};
const handleSubmit = (e: any) => { 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 */ /* Password has custom validation */
if (!formState.password.valid) { if (!formState.password.valid) {
// if (passwordInput.current){ // if (passwordInput.current){
// passwordInput.current.focus() // passwordInput.current.focus()
// } // }
// return // return
} }
const payload = { const payload = {
@ -71,18 +73,16 @@ const Signup = (props:SignUpProps) => {
password: formState.password, password: formState.password,
email_opt_in: formState.emailOptIn.value, email_opt_in: formState.emailOptIn.value,
// plan, // Pass it along if on QS, won't have any effect unless on multitenancy // plan, // Pass it along if on QS, won't have any effect unless on multitenancy
} };
// createAccount(payload) // createAccount(payload)
// axios.get(`https://jsonplaceholder.typicode.com/users`) // axios.get(`https://jsonplaceholder.typicode.com/users`)
// .then(res => { // .then(res => {
// console.log(res); // console.log(res);
// console.log(res.data); // console.log(res.data);
// }) // })
let texttolog = JSON.stringify(payload) let texttolog = JSON.stringify(payload);
// submitForm.get('sendMessage', { // submitForm.get('sendMessage', {
// params: { // params: {
@ -95,44 +95,57 @@ const Signup = (props:SignUpProps) => {
// console.log(res.data); // console.log(res.data);
// }) // })
submitForm.post("user?email=" + texttolog).then((res) => {
submitForm.post('user?email='+texttolog
).then(res => {
console.log(res); console.log(res);
console.log(res.data); console.log(res.data);
}) });
localStorage.setItem('isLoggedIn', 'yes'); localStorage.setItem("isLoggedIn", "yes");
props.history.push('/application') props.history.push("/application");
}; };
return ( return (
<div className="signup-form"> <div className="signup-form">
<Space direction="vertical" className="space-top" style={{ width: '100%', paddingLeft: 32 }}> <Space
<h1 className="title" style={{ marginBottom: 0, marginTop: 40, display: 'flex', alignItems: 'center' }}> 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 }} /> */} {/* <img src={"Signoz-white.svg"} alt="" style={{ height: 60 }} /> */}
Create your account Create your account
</h1> </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> </Space>
<Row style={{ display: 'flex', justifyContent: 'center' }}> <Row style={{ display: "flex", justifyContent: "center" }}>
<div style={{ display: 'flex', alignItems: 'center', flexDirection: 'column' }}> <div
style={{ display: "flex", alignItems: "center", flexDirection: "column" }}
>
<img <img
src={"signoz.svg"} src={"signoz.svg"}
style={{ maxHeight: '100%', maxWidth: 300, marginTop: 64 }} style={{ maxHeight: "100%", maxWidth: 300, marginTop: 64 }}
alt="" alt=""
className="main-img" className="main-img"
/> />
</div> </div>
<div <div
style={{ style={{
display: 'flex', display: "flex",
justifyContent: 'flex-start', justifyContent: "flex-start",
margin: '0 32px', margin: "0 32px",
flexDirection: 'column', flexDirection: "column",
paddingTop: 32, paddingTop: 32,
maxWidth: '32rem', maxWidth: "32rem",
}} }}
> >
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
@ -142,18 +155,22 @@ const Signup = (props:SignUpProps) => {
placeholder="mike@netflix.com" placeholder="mike@netflix.com"
type="email" type="email"
value={formState.email.value} value={formState.email.value}
onChange={(e) => updateForm('email', e.target)} onChange={(e) => updateForm("email", e.target)}
required required
// disabled={accountLoading} // disabled={accountLoading}
id="signupEmail" id="signupEmail"
/> />
</div> </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> <label htmlFor="signupPassword">Password</label>
<Input.Password <Input.Password
value={formState.password.value} value={formState.password.value}
onChange={(e) => updateForm('password', e.target)} onChange={(e) => updateForm("password", e.target)}
required required
ref={passwordInput} ref={passwordInput}
// disabled={accountLoading} // disabled={accountLoading}
@ -163,7 +180,9 @@ const Signup = (props:SignUpProps) => {
{/* <PasswordStrength password={formState.password.value} /> */} {/* <PasswordStrength password={formState.password.value} /> */}
</Suspense> </Suspense>
{!formState.password && ( {!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> </div>
@ -173,7 +192,7 @@ const Signup = (props:SignUpProps) => {
placeholder="Mike" placeholder="Mike"
autoFocus autoFocus
value={formState.firstName.value} value={formState.firstName.value}
onChange={(e) => updateForm('firstName', e.target)} onChange={(e) => updateForm("firstName", e.target)}
required required
// disabled={accountLoading} // disabled={accountLoading}
id="signupFirstName" id="signupFirstName"
@ -185,7 +204,7 @@ const Signup = (props:SignUpProps) => {
<Input <Input
placeholder="Netflix" placeholder="Netflix"
value={formState.companyName.value} value={formState.companyName.value}
onChange={(e) => updateForm('companyName', e.target)} onChange={(e) => updateForm("companyName", e.target)}
// disabled={accountLoading} // disabled={accountLoading}
id="signupCompanyName" id="signupCompanyName"
/> />
@ -194,11 +213,11 @@ const Signup = (props:SignUpProps) => {
<div> <div>
<Checkbox <Checkbox
checked={formState.emailOptIn.value} checked={formState.emailOptIn.value}
onChange={(e) => updateForm('emailOptIn', e.target, 'checked')} onChange={(e) => updateForm("emailOptIn", e.target, "checked")}
// disabled={accountLoading} // disabled={accountLoading}
> >
Send me occasional emails about security and product updates. You may unsubscribe at any Send me occasional emails about security and product updates. You may
time. unsubscribe at any time.
</Checkbox> </Checkbox>
</div> </div>
<div className="text-center space-top"> <div className="text-center space-top">
@ -228,8 +247,7 @@ const Signup = (props:SignUpProps) => {
</div> </div>
</Row> </Row>
</div> </div>
); );
} };
export default withRouter(Signup); export default withRouter(Signup);

View File

@ -1,19 +1,22 @@
import React from 'react'; import React from "react";
import { Line as ChartJSLine } from 'react-chartjs-2'; import { Line as ChartJSLine } from "react-chartjs-2";
import { ChartOptions } from 'chart.js'; import { ChartOptions } from "chart.js";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from "react-router-dom";
import styled from 'styled-components'; import styled from "styled-components";
import { metricItem } from '../../actions/metrics' import { metricItem } from "../../actions/metrics";
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>` const ChartPopUpUnique = styled.div<{
ycoordinate: number;
xcoordinate: number;
}>`
background-color: white; background-color: white;
border: 1px solid rgba(219, 112, 147, 0.5); border: 1px solid rgba(219, 112, 147, 0.5);
zIndex:10; zindex: 10;
position: absolute; position: absolute;
top:${props => props.ycoordinate}px; top: ${(props) => props.ycoordinate}px;
left:${props => props.xcoordinate}px; left: ${(props) => props.xcoordinate}px;
font-size: 12px; font-size: 12px;
border-radius: 2px; border-radius: 2px;
`; `;
@ -28,67 +31,58 @@ padding-right:4px;
} }
`; `;
// PNOTE - Check if this should be the case // PNOTE - Check if this should be the case
const theme = 'dark'; const theme = "dark";
interface ErrorRateChartProps extends RouteComponentProps<any> { interface ErrorRateChartProps extends RouteComponentProps<any> {
data : metricItem[], data: metricItem[];
} }
interface ErrorRateChart { interface ErrorRateChart {
chartRef: any; chartRef: any;
} }
class ErrorRateChart extends React.Component<ErrorRateChartProps> { class ErrorRateChart extends React.Component<ErrorRateChartProps> {
constructor(props: ErrorRateChartProps) { constructor(props: ErrorRateChartProps) {
super(props); super(props);
this.chartRef = React.createRef(); this.chartRef = React.createRef();
} }
state = { state = {
// data: props.data, // data: props.data,
xcoordinate: 0, xcoordinate: 0,
ycoordinate: 0, ycoordinate: 0,
showpopUp: false, showpopUp: false,
// graphInfo:{} // graphInfo:{}
} };
onClickhandler = async (e: any, event: any) => { onClickhandler = async (e: any, event: any) => {
var firstPoint; var firstPoint;
if (this.chartRef) { if (this.chartRef) {
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0]; firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
} }
if (firstPoint) if (firstPoint) {
{// PNOTE - TODO - Is await needed in this expression? // PNOTE - TODO - Is await needed in this expression?
await this.setState({ await this.setState({
xcoordinate: e.offsetX + 20, xcoordinate: e.offsetX + 20,
ycoordinate: e.offsetY, ycoordinate: e.offsetY,
showpopUp: true, showpopUp: true,
// graphInfo:{...event} // graphInfo:{...event}
}) });
}
} }
};
gotoTracesHandler = () => { gotoTracesHandler = () => {
this.props.history.push('/traces') this.props.history.push("/traces");
} };
gotoAlertsHandler = () => { 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 // PNOTE - Keeping service map for now, will replace with alerts when alert page is made
} };
options_charts: ChartOptions = { options_charts: ChartOptions = {
onClick: this.onClickhandler, onClick: this.onClickhandler,
maintainAspectRatio: true, maintainAspectRatio: true,
@ -96,49 +90,44 @@ class ErrorRateChart extends React.Component<ErrorRateChartProps>{
title: { title: {
display: true, display: true,
text: 'Error per sec', text: "Error per sec",
fontSize: 20, fontSize: 20,
position:'top', position: "top",
padding: 2, padding: 2,
fontFamily: 'Arial', fontFamily: "Arial",
fontStyle: 'regular', fontStyle: "regular",
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' , fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
}, },
legend: { legend: {
display: true, display: true,
position: 'bottom', position: "bottom",
align: 'center', align: "center",
labels: { 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, fontSize: 10,
boxWidth: 10, boxWidth: 10,
usePointStyle: true, usePointStyle: true,
},
}
}, },
tooltips: { tooltips: {
mode: 'label', mode: "label",
bodyFontSize: 12, bodyFontSize: 12,
titleFontSize: 12, titleFontSize: 12,
callbacks: { callbacks: {
label: function (tooltipItem, data) { label: function (tooltipItem, data) {
if (typeof tooltipItem.yLabel === "number") {
if (typeof(tooltipItem.yLabel) === 'number') return (
{ data.datasets![tooltipItem.datasetIndex!].label +
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2); " : " +
tooltipItem.yLabel.toFixed(2)
);
} else {
return "";
} }
else
{
return '';
}
}, },
}, },
}, },
@ -165,15 +154,16 @@ class ErrorRateChart extends React.Component<ErrorRateChartProps>{
borderDash: [1, 4], borderDash: [1, 4],
color: "#D3D3D3", color: "#D3D3D3",
lineWidth: 0.25, lineWidth: 0.25,
} },
}, },
], ],
xAxes: [{ xAxes: [
type: 'time', {
type: "time",
// time: { // time: {
// unit: 'second' // unit: 'second'
// }, // },
distribution:'linear', distribution: "linear",
ticks: { ticks: {
beginAtZero: false, beginAtZero: false,
fontSize: 10, fontSize: 10,
@ -181,60 +171,58 @@ class ErrorRateChart extends React.Component<ErrorRateChartProps>{
maxTicksLimit: 10, maxTicksLimit: 10,
}, },
// gridLines: false, --> not a valid option // gridLines: false, --> not a valid option
}]
}, },
} ],
},
};
GraphTracePopUp = () => { GraphTracePopUp = () => {
if (this.state.showpopUp) { if (this.state.showpopUp) {
return ( return (
<ChartPopUpUnique
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}> xcoordinate={this.state.xcoordinate}
ycoordinate={this.state.ycoordinate}
>
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements> <PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements> <PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
</ChartPopUpUnique> </ChartPopUpUnique>
);
) } else return null;
} };
else
return null;
}
render() { render() {
const ndata = this.props.data; const ndata = this.props.data;
const data_chartJS = (canvas: any) => { const data_chartJS = (canvas: any) => {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
const gradient = ctx.createLinearGradient(0, 0, 0, 100); const gradient = ctx.createLinearGradient(0, 0, 0, 100);
gradient.addColorStop(0, 'rgba(250,174,50,1)'); gradient.addColorStop(0, "rgba(250,174,50,1)");
gradient.addColorStop(1, '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 return {
datasets: [{ labels: ndata.map((s) => new Date(s.timestamp / 1000000)), // converting from nano second to mili second
label: 'Errors per sec', datasets: [
data: ndata.map(s => s.errorRate), {
label: "Errors per sec",
data: ndata.map((s) => s.errorRate),
pointRadius: 0.5, 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, borderWidth: 2,
}, },
],
]} };
}; };
return ( return (
<div> <div>
{this.GraphTracePopUp()} {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> </div>
); );
} }
} }
export default withRouter(ErrorRateChart); export default withRouter(ErrorRateChart);

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from "react";
import { Bar, Line as ChartJSLine } from 'react-chartjs-2'; import { Bar, Line as ChartJSLine } from "react-chartjs-2";
import styled from 'styled-components'; import styled from "styled-components";
import { customMetricsItem } from '../../actions/metrics' import { customMetricsItem } from "../../actions/metrics";
const GenVisualizationWrapper = styled.div` const GenVisualizationWrapper = styled.div`
height: 160px; height: 160px;
@ -14,15 +14,15 @@ interface GenericVisualizationsProps {
} }
const GenericVisualizations = (props: GenericVisualizationsProps) => { const GenericVisualizations = (props: GenericVisualizationsProps) => {
const data = { const data = {
labels: props.data.map(s => new Date(s.timestamp/1000000)), labels: props.data.map((s) => new Date(s.timestamp / 1000000)),
datasets: [{ datasets: [
data: props.data.map(s => s.value), {
borderColor: 'rgba(250,174,50,1)',// for line chart data: props.data.map((s) => s.value),
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 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 = { const options = {
@ -32,16 +32,19 @@ const GenericVisualizations = (props: GenericVisualizationsProps) => {
display: false, display: false,
}, },
scales: { scales: {
yAxes: [{ yAxes: [
{
gridLines: { gridLines: {
drawBorder: false, drawBorder: false,
}, },
ticks: { ticks: {
display: false display: false,
} },
}], },
xAxes: [{ ],
type: 'time', xAxes: [
{
type: "time",
// distribution: 'linear', // distribution: 'linear',
//'linear': data are spread according to their time (distances can vary) //'linear': data are spread according to their time (distances can vary)
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html // From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
@ -52,29 +55,24 @@ const GenericVisualizations = (props: GenericVisualizationsProps) => {
maxTicksLimit: 10, maxTicksLimit: 10,
}, },
// gridLines: false, --> not a valid option // gridLines: false, --> not a valid option
}], },
],
}, },
}; };
if(props.chartType === 'line') if (props.chartType === "line") {
{
return ( return (
<GenVisualizationWrapper> <GenVisualizationWrapper>
<ChartJSLine data={data} options={options} /> <ChartJSLine data={data} options={options} />
</GenVisualizationWrapper> </GenVisualizationWrapper>
); );
} else if (props.chartType === "bar") {
} else if (props.chartType === 'bar')
{
return ( return (
<GenVisualizationWrapper> <GenVisualizationWrapper>
<Bar data={data} options={options} /> <Bar data={data} options={options} />
</GenVisualizationWrapper> </GenVisualizationWrapper>
); );
} } else return null;
else };
return null;
}
export default GenericVisualizations; export default GenericVisualizations;

View File

@ -1,19 +1,22 @@
import React from 'react'; import React from "react";
import { Line as ChartJSLine } from 'react-chartjs-2'; import { Line as ChartJSLine } from "react-chartjs-2";
import { ChartOptions } from 'chart.js'; import { ChartOptions } from "chart.js";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from "react-router-dom";
import styled from 'styled-components'; import styled from "styled-components";
import { metricItem } from '../../actions/metrics' import { metricItem } from "../../actions/metrics";
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>` const ChartPopUpUnique = styled.div<{
ycoordinate: number;
xcoordinate: number;
}>`
background-color: white; background-color: white;
border: 1px solid rgba(219, 112, 147, 0.5); border: 1px solid rgba(219, 112, 147, 0.5);
zIndex:10; zindex: 10;
position: absolute; position: absolute;
top:${props => props.ycoordinate}px; top: ${(props) => props.ycoordinate}px;
left:${props => props.xcoordinate}px; left: ${(props) => props.xcoordinate}px;
font-size: 12px; font-size: 12px;
border-radius: 2px; border-radius: 2px;
`; `;
@ -28,78 +31,64 @@ padding-right:4px;
} }
`; `;
const theme = "dark";
const theme = 'dark';
interface LatencyLineChartProps extends RouteComponentProps<any> { interface LatencyLineChartProps extends RouteComponentProps<any> {
data : metricItem[], data: metricItem[];
popupClickHandler: Function, popupClickHandler: Function;
} }
interface LatencyLineChart { interface LatencyLineChart {
chartRef: any; chartRef: any;
} }
class LatencyLineChart extends React.Component<LatencyLineChartProps> { class LatencyLineChart extends React.Component<LatencyLineChartProps> {
constructor(props: LatencyLineChartProps) { constructor(props: LatencyLineChartProps) {
super(props); super(props);
this.chartRef = React.createRef(); this.chartRef = React.createRef();
} }
state = { state = {
xcoordinate: 0, xcoordinate: 0,
ycoordinate: 0, ycoordinate: 0,
showpopUp: false, showpopUp: false,
firstpoint_ts: 0, firstpoint_ts: 0,
// graphInfo:{} // graphInfo:{}
} };
onClickhandler = async (e: any, event: any) => { onClickhandler = async (e: any, event: any) => {
var firstPoint; var firstPoint;
if (this.chartRef) { if (this.chartRef) {
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0]; firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
} }
if (firstPoint) if (firstPoint) {
{
this.setState({ this.setState({
xcoordinate: e.offsetX + 20, xcoordinate: e.offsetX + 20,
ycoordinate: e.offsetY, ycoordinate: e.offsetY,
showpopUp: true, showpopUp: true,
firstpoint_ts: this.props.data[firstPoint._index].timestamp, firstpoint_ts: this.props.data[firstPoint._index].timestamp,
// graphInfo:{...event} // graphInfo:{...event}
}) });
} } else {
else
{
// if clicked outside of the graph line, then firstpoint is undefined -> close popup. // 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 // Only works for clicking in the same chart - as click handler only detects clicks in that chart
this.setState({ this.setState({
showpopUp: false, showpopUp: false,
});
})
}
} }
};
gotoTracesHandler = (xc: any) => { gotoTracesHandler = (xc: any) => {
this.props.history.push('/traces') this.props.history.push("/traces");
} };
gotoAlertsHandler = () => { 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 // PNOTE - Keeping service map for now, will replace with alerts when alert page is made
} };
options_charts: ChartOptions = { options_charts: ChartOptions = {
onClick: this.onClickhandler, onClick: this.onClickhandler,
maintainAspectRatio: true, maintainAspectRatio: true,
@ -107,45 +96,43 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps>{
title: { title: {
display: true, display: true,
text: 'Application Latency in ms', text: "Application Latency in ms",
fontSize: 20, fontSize: 20,
position:'top', position: "top",
padding: 8, padding: 8,
fontFamily: 'Arial', fontFamily: "Arial",
fontStyle: 'regular', fontStyle: "regular",
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' , fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
}, },
legend: { legend: {
display: true, display: true,
position: 'bottom', position: "bottom",
align: 'center', align: "center",
labels: { 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, fontSize: 10,
boxWidth: 10, boxWidth: 10,
usePointStyle: true, usePointStyle: true,
},
}
}, },
tooltips: { tooltips: {
mode: 'label', mode: "label",
bodyFontSize: 12, bodyFontSize: 12,
titleFontSize: 12, titleFontSize: 12,
callbacks: { callbacks: {
label: function (tooltipItem, data) { label: function (tooltipItem, data) {
if (typeof tooltipItem.yLabel === "number") {
if (typeof(tooltipItem.yLabel) === 'number') return (
{ data.datasets![tooltipItem.datasetIndex!].label +
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2); " : " +
} tooltipItem.yLabel.toFixed(2)
else );
{ } else {
return ''; return "";
} }
}, },
}, },
@ -167,15 +154,16 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps>{
borderDash: [1, 4], borderDash: [1, 4],
color: "#D3D3D3", color: "#D3D3D3",
lineWidth: 0.25, lineWidth: 0.25,
} },
}, },
], ],
xAxes: [{ xAxes: [
type: 'time', {
type: "time",
// time: { // time: {
// unit: 'second' // unit: 'second'
// }, // },
distribution: 'linear', distribution: "linear",
//'linear': data are spread according to their time (distances can vary) //'linear': data are spread according to their time (distances can vary)
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html // From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
ticks: { ticks: {
@ -185,73 +173,76 @@ class LatencyLineChart extends React.Component<LatencyLineChartProps>{
maxTicksLimit: 10, maxTicksLimit: 10,
}, },
// gridLines: false, --> not a valid option // gridLines: false, --> not a valid option
}]
}, },
} ],
},
};
GraphTracePopUp = () => { GraphTracePopUp = () => {
if (this.state.showpopUp) { if (this.state.showpopUp) {
return ( return (
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}> <ChartPopUpUnique
<PopUpElements onClick={() => this.props.popupClickHandler(this.state.firstpoint_ts)}>View Traces</PopUpElements> 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> <PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
</ChartPopUpUnique> </ChartPopUpUnique>
);
} else return null;
) };
}
else
return null;
}
render() { render() {
const ndata = this.props.data; const ndata = this.props.data;
const data_chartJS = (canvas: any) => { const data_chartJS = (canvas: any) => {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
const gradient = ctx.createLinearGradient(0, 0, 0, 100); const gradient = ctx.createLinearGradient(0, 0, 0, 100);
gradient.addColorStop(0, 'rgba(250,174,50,1)'); gradient.addColorStop(0, "rgba(250,174,50,1)");
gradient.addColorStop(1, 'rgba(250,174,50,1)'); gradient.addColorStop(1, "rgba(250,174,50,1)");
return{ labels: ndata.map(s => new Date(s.timestamp/1000000)), return {
datasets: [{ labels: ndata.map((s) => new Date(s.timestamp / 1000000)),
label: 'p99 Latency', datasets: [
data: ndata.map(s => s.p99/1000000), //converting latency from nano sec to ms {
label: "p99 Latency",
data: ndata.map((s) => s.p99 / 1000000), //converting latency from nano sec to ms
pointRadius: 0.5, 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, borderWidth: 2,
}, },
{ {
label: 'p90 Latency', label: "p90 Latency",
data: ndata.map(s => s.p90/1000000), //converting latency from nano sec to ms data: ndata.map((s) => s.p90 / 1000000), //converting latency from nano sec to ms
pointRadius: 0.5, pointRadius: 0.5,
borderColor: 'rgba(227, 74, 51, 1.0)', borderColor: "rgba(227, 74, 51, 1.0)",
borderWidth: 2, borderWidth: 2,
}, },
{ {
label: 'p50 Latency', label: "p50 Latency",
data: ndata.map(s => s.p50/1000000), //converting latency from nano sec to ms data: ndata.map((s) => s.p50 / 1000000), //converting latency from nano sec to ms
pointRadius: 0.5, pointRadius: 0.5,
borderColor: 'rgba(57, 255, 20, 1.0)', borderColor: "rgba(57, 255, 20, 1.0)",
borderWidth: 2, borderWidth: 2,
}, },
]} ],
};
}; };
return ( return (
<div> <div>
{this.GraphTracePopUp()} {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> </div>
); );
} }
} }
export default withRouter(LatencyLineChart); export default withRouter(LatencyLineChart);

View File

@ -1,19 +1,22 @@
import React from 'react'; import React from "react";
import { Line as ChartJSLine } from 'react-chartjs-2'; import { Line as ChartJSLine } from "react-chartjs-2";
import { ChartOptions } from 'chart.js'; import { ChartOptions } from "chart.js";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from "react-router-dom";
import styled from 'styled-components'; import styled from "styled-components";
import { metricItem } from '../../actions/metrics' import { metricItem } from "../../actions/metrics";
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>` const ChartPopUpUnique = styled.div<{
ycoordinate: number;
xcoordinate: number;
}>`
background-color: white; background-color: white;
border: 1px solid rgba(219, 112, 147, 0.5); border: 1px solid rgba(219, 112, 147, 0.5);
zIndex:10; zindex: 10;
position: absolute; position: absolute;
top:${props => props.ycoordinate}px; top: ${(props) => props.ycoordinate}px;
left:${props => props.xcoordinate}px; left: ${(props) => props.xcoordinate}px;
font-size: 12px; font-size: 12px;
border-radius: 2px; border-radius: 2px;
`; `;
@ -28,20 +31,17 @@ padding-right:4px;
} }
`; `;
const theme = "dark";
const theme = 'dark';
interface RequestRateChartProps extends RouteComponentProps<any> { interface RequestRateChartProps extends RouteComponentProps<any> {
data : metricItem[], data: metricItem[];
} }
interface RequestRateChart { interface RequestRateChart {
chartRef: any; chartRef: any;
} }
class RequestRateChart extends React.Component<RequestRateChartProps> { class RequestRateChart extends React.Component<RequestRateChartProps> {
constructor(props: RequestRateChartProps) { constructor(props: RequestRateChartProps) {
super(props); super(props);
this.chartRef = React.createRef(); this.chartRef = React.createRef();
@ -52,39 +52,35 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
ycoordinate: 0, ycoordinate: 0,
showpopUp: false, showpopUp: false,
// graphInfo:{} // graphInfo:{}
} };
onClickhandler = async (e: any, event: any) => { onClickhandler = async (e: any, event: any) => {
var firstPoint; var firstPoint;
if (this.chartRef) { if (this.chartRef) {
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0]; firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
} }
if (firstPoint) if (firstPoint) {
{// PNOTE - TODO - Is await needed in this expression? // PNOTE - TODO - Is await needed in this expression?
await this.setState({ await this.setState({
xcoordinate: e.offsetX + 20, xcoordinate: e.offsetX + 20,
ycoordinate: e.offsetY, ycoordinate: e.offsetY,
showpopUp: true, showpopUp: true,
// graphInfo:{...event} // graphInfo:{...event}
}) });
}
} }
};
gotoTracesHandler = () => { gotoTracesHandler = () => {
this.props.history.push('/traces') this.props.history.push("/traces");
} };
gotoAlertsHandler = () => { 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 // PNOTE - Keeping service map for now, will replace with alerts when alert page is made
} };
options_charts: ChartOptions = { options_charts: ChartOptions = {
onClick: this.onClickhandler, onClick: this.onClickhandler,
maintainAspectRatio: true, maintainAspectRatio: true,
@ -92,45 +88,44 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
title: { title: {
display: true, display: true,
text: 'Request per sec', text: "Request per sec",
fontSize: 20, fontSize: 20,
position:'top', position: "top",
padding: 2, padding: 2,
fontFamily: 'Arial', fontFamily: "Arial",
fontStyle: 'regular', fontStyle: "regular",
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' , fontColor: theme === "dark" ? "rgb(200, 200, 200)" : "rgb(20, 20, 20)",
}, },
legend: { legend: {
display: true, display: true,
position: 'bottom', position: "bottom",
align: 'center', align: "center",
labels: { 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, fontSize: 10,
boxWidth: 10, boxWidth: 10,
usePointStyle: true, usePointStyle: true,
} },
}, },
tooltips: { tooltips: {
mode: 'label', mode: "label",
bodyFontSize: 12, bodyFontSize: 12,
titleFontSize: 12, titleFontSize: 12,
callbacks: { callbacks: {
label: function (tooltipItem, data) { label: function (tooltipItem, data) {
if (typeof tooltipItem.yLabel === "number") {
if (typeof(tooltipItem.yLabel) === 'number') return (
{ data.datasets![tooltipItem.datasetIndex!].label +
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(2); " : " +
tooltipItem.yLabel.toFixed(2)
);
} else {
return "";
} }
else
{
return '';
}
}, },
}, },
}, },
@ -146,21 +141,21 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
maxTicksLimit: 6, maxTicksLimit: 6,
}, },
gridLines: { gridLines: {
// You can change the color, the dash effect, the main axe color, etc. // You can change the color, the dash effect, the main axe color, etc.
borderDash: [1, 4], borderDash: [1, 4],
color: "#D3D3D3", color: "#D3D3D3",
lineWidth: 0.25, lineWidth: 0.25,
} },
}, },
], ],
xAxes: [{ xAxes: [
type: 'time', {
type: "time",
// time: { // time: {
// unit: 'second' // unit: 'second'
// }, // },
distribution:'linear', distribution: "linear",
ticks: { ticks: {
beginAtZero: false, beginAtZero: false,
fontSize: 10, fontSize: 10,
@ -168,56 +163,58 @@ class RequestRateChart extends React.Component<RequestRateChartProps>{
maxTicksLimit: 10, maxTicksLimit: 10,
}, },
// gridLines: false, --> not a valid option // gridLines: false, --> not a valid option
}]
}, },
} ],
},
};
GraphTracePopUp = () => { GraphTracePopUp = () => {
if (this.state.showpopUp) { if (this.state.showpopUp) {
return ( 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.gotoTracesHandler}>View Traces</PopUpElements>
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements> <PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
</ChartPopUpUnique> </ChartPopUpUnique>
) );
} } else return null;
else };
return null;
}
render() { render() {
const ndata = this.props.data; const ndata = this.props.data;
const data_chartJS = (canvas: any) => { const data_chartJS = (canvas: any) => {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
const gradient = ctx.createLinearGradient(0, 0, 0, 100); const gradient = ctx.createLinearGradient(0, 0, 0, 100);
gradient.addColorStop(0, 'rgba(250,174,50,1)'); gradient.addColorStop(0, "rgba(250,174,50,1)");
gradient.addColorStop(1, 'rgba(250,174,50,1)'); gradient.addColorStop(1, "rgba(250,174,50,1)");
return{labels: ndata.map(s => new Date(s.timestamp/1000000)), return {
datasets: [{ labels: ndata.map((s) => new Date(s.timestamp / 1000000)),
label: 'Request per sec', datasets: [
data: ndata.map(s => s.callRate), {
label: "Request per sec",
data: ndata.map((s) => s.callRate),
pointRadius: 0.5, 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, borderWidth: 2,
}, },
],
]} };
}; };
return ( return (
<div> <div>
{this.GraphTracePopUp()} {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> </div>
); );
} }
} }
export default withRouter(RequestRateChart); export default withRouter(RequestRateChart);

View File

@ -1,31 +1,36 @@
import React,{useEffect} from 'react'; import React, { useEffect } from "react";
import { Tabs, Card, Row, Col} from 'antd'; import { Tabs, Card, Row, Col } from "antd";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { useParams, RouteComponentProps } from "react-router-dom"; import { useParams, RouteComponentProps } from "react-router-dom";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { getServicesMetrics, metricItem, getTopEndpoints, topEndpointListItem, GlobalTime, updateTimeInterval } from '../../actions'; import {
import { StoreState } from '../../reducers' getServicesMetrics,
import LatencyLineChart from "./LatencyLineChart" metricItem,
import RequestRateChart from './RequestRateChart' getTopEndpoints,
import ErrorRateChart from './ErrorRateChart' topEndpointListItem,
import TopEndpointsTable from './TopEndpointsTable'; GlobalTime,
updateTimeInterval,
} from "../../actions";
import { StoreState } from "../../reducers";
import LatencyLineChart from "./LatencyLineChart";
import RequestRateChart from "./RequestRateChart";
import ErrorRateChart from "./ErrorRateChart";
import TopEndpointsTable from "./TopEndpointsTable";
const { TabPane } = Tabs; const { TabPane } = Tabs;
interface ServicesMetricsProps extends RouteComponentProps<any> { interface ServicesMetricsProps extends RouteComponentProps<any> {
serviceMetrics: metricItem[], serviceMetrics: metricItem[];
getServicesMetrics: Function, getServicesMetrics: Function;
topEndpointsList: topEndpointListItem[], topEndpointsList: topEndpointListItem[];
getTopEndpoints: Function, getTopEndpoints: Function;
globalTime: GlobalTime, globalTime: GlobalTime;
updateTimeInterval: Function, updateTimeInterval: Function;
} }
const _ServiceMetrics = (props: ServicesMetricsProps) => { const _ServiceMetrics = (props: ServicesMetricsProps) => {
const params = useParams<{ servicename?: string }>();
const params = useParams<{ servicename?: string; }>();
useEffect(() => { useEffect(() => {
props.getServicesMetrics(params.servicename, props.globalTime); props.getServicesMetrics(params.servicename, props.globalTime);
@ -33,18 +38,22 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
}, [props.globalTime, params.servicename]); }, [props.globalTime, params.servicename]);
const onTracePopupClick = (timestamp: number) => { const onTracePopupClick = (timestamp: number) => {
props.updateTimeInterval("custom", [
props.updateTimeInterval('custom',[(timestamp/1000000)-5*60*1000,(timestamp/1000000)])// updateTimeInterval takes second range in ms -- give -5 min to selected time, timestamp / 1000000 - 5 * 60 * 1000,
props.history.push('/traces') timestamp / 1000000,
} ]); // updateTimeInterval takes second range in ms -- give -5 min to selected time,
props.history.push("/traces");
};
return ( return (
<Tabs defaultActiveKey="1"> <Tabs defaultActiveKey="1">
<TabPane tab="Application Metrics" key="1"> <TabPane tab="Application Metrics" key="1">
<Row gutter={32} style={{ margin: 20 }}> <Row gutter={32} style={{ margin: 20 }}>
<Col span={12}> <Col span={12}>
<Card bodyStyle={{ padding: 10 }}> <Card bodyStyle={{ padding: 10 }}>
<LatencyLineChart data={props.serviceMetrics} popupClickHandler={onTracePopupClick} /> <LatencyLineChart
data={props.serviceMetrics}
popupClickHandler={onTracePopupClick}
/>
</Card> </Card>
</Col> </Col>
@ -72,14 +81,15 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
<TabPane tab="External Calls" key="2"> <TabPane tab="External Calls" key="2">
<div style={{ margin: 20 }}> Coming Soon.. </div> <div style={{ margin: 20 }}> Coming Soon.. </div>
<div className="container" style={{ display: 'flex', flexFlow: 'column wrap' }}> <div
className="container"
<div className='row'> style={{ display: "flex", flexFlow: "column wrap" }}
<div className='col-md-6 col-sm-12 col-12'> >
<div className="row">
<div className="col-md-6 col-sm-12 col-12">
{/* <ChartJSLineChart data={this.state.graphData} /> */} {/* <ChartJSLineChart data={this.state.graphData} /> */}
</div> </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} /> */} {/* <ChartJSLineChart data={this.state.graphData} /> */}
</div> </div>
</div> </div>
@ -87,17 +97,26 @@ const _ServiceMetrics = (props: ServicesMetricsProps) => {
</TabPane> </TabPane>
</Tabs> </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, getServicesMetrics: getServicesMetrics,
getTopEndpoints: getTopEndpoints, getTopEndpoints: getTopEndpoints,
updateTimeInterval: updateTimeInterval, updateTimeInterval: updateTimeInterval,
})(_ServiceMetrics),
})(_ServiceMetrics)); );

View File

@ -1 +1 @@
export { ServiceMetrics as default } from './ServiceMetrics'; export { ServiceMetrics as default } from "./ServiceMetrics";

View File

@ -1,18 +1,17 @@
import React, {useEffect} from 'react'; import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { NavLink } from 'react-router-dom' import { NavLink } from "react-router-dom";
import { Table } from 'antd'; import { Spin, Table } from "antd";
import styled from 'styled-components'; import styled from "styled-components";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { getServicesList, GlobalTime, servicesListItem } from "../../actions";
import { getServicesList, GlobalTime, servicesListItem } from '../../actions'; import { StoreState } from "../../reducers";
import { StoreState } from '../../reducers'
interface ServicesTableProps { interface ServicesTableProps {
servicesList: servicesListItem[], servicesList: servicesListItem[];
getServicesList: Function, getServicesList: Function;
globalTime: GlobalTime, globalTime: GlobalTime;
} }
const Wrapper = styled.div` const Wrapper = styled.div`
@ -20,68 +19,102 @@ padding-top:40px;
padding-bottom: 40px; padding-bottom: 40px;
padding-left: 40px; padding-left: 40px;
padding-right: 40px; padding-right: 40px;
.ant-table table { font-size: 12px; }; .ant-table table {
.ant-table tfoot>tr>td, .ant-table tfoot>tr>th, .ant-table-tbody>tr>td, .ant-table-thead>tr>th { padding: 10px; }; font-size: 12px;
}
.ant-table tfoot > tr > td,
.ant-table tfoot > tr > th,
.ant-table-tbody > tr > td,
.ant-table-thead > tr > th {
padding: 10px;
}
`; `;
const TableLoadingWrapper = styled.div`
display: flex;
justify-content: center;
margin-top: 80px;
`
const LoadingText = styled.div`
margin-left: 16px;
`
const columns = [ const columns = [
{ {
title: 'Application', title: "Application",
dataIndex: 'serviceName', dataIndex: "serviceName",
key: 'serviceName', key: "serviceName",
render: (text :string) => <NavLink style={{textTransform:'capitalize'}} to={'/application/' + text}><strong>{text}</strong></NavLink>, render: (text: string) => (
<NavLink style={{ textTransform: "capitalize" }} to={"/application/" + text}>
<strong>{text}</strong>
</NavLink>
),
}, },
{ {
title: 'P99 latency (in ms)', title: "P99 latency (in ms)",
dataIndex: 'p99', dataIndex: "p99",
key: 'p99', key: "p99",
sorter: (a: any, b: any) => a.p99 - b.p99, sorter: (a: any, b: any) => a.p99 - b.p99,
// sortDirections: ['descend', 'ascend'], // sortDirections: ['descend', 'ascend'],
render: (value: number) => (value / 1000000).toFixed(2), render: (value: number) => (value / 1000000).toFixed(2),
}, },
{ {
title: 'Error Rate (in %)', title: "Error Rate (in %)",
dataIndex: 'errorRate', dataIndex: "errorRate",
key: 'errorRate', key: "errorRate",
sorter: (a: any, b: any) => a.errorRate - b.errorRate, sorter: (a: any, b: any) => a.errorRate - b.errorRate,
// sortDirections: ['descend', 'ascend'], // sortDirections: ['descend', 'ascend'],
render: (value: number) => (value * 100).toFixed(2), render: (value: number) => (value * 100).toFixed(2),
}, },
{ {
title: 'Requests Per Second', title: "Requests Per Second",
dataIndex: 'callRate', dataIndex: "callRate",
key: 'callRate', key: "callRate",
sorter: (a: any, b: any) => a.callRate - b.callRate, sorter: (a: any, b: any) => a.callRate - b.callRate,
// sortDirections: ['descend', 'ascend'], // sortDirections: ['descend', 'ascend'],
render: (value: number) => value.toFixed(2), render: (value: number) => value.toFixed(2),
}, },
]; ];
const _ServicesTable = (props: ServicesTableProps) => { const _ServicesTable = (props: ServicesTableProps) => {
const search = useLocation().search; 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(() => { 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]); }, [props.globalTime]);
if(!dataFetched){
return ( return (
<TableLoadingWrapper>
<Wrapper> <Spin/>
<Table dataSource={props.servicesList} columns={columns} pagination={false} /> <LoadingText>Fetching data</LoadingText>
</Wrapper> </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 }; return { servicesList: state.servicesList, globalTime: state.globalTime };
}; };

View File

@ -1 +1 @@
export { ServicesTable as default } from './ServicesTable'; export { ServicesTable as default } from "./ServicesTable";

View File

@ -1,63 +1,66 @@
import React from 'react'; import React from "react";
import { NavLink } from 'react-router-dom'; import { NavLink } from "react-router-dom";
import { Table } from 'antd' import { Table } from "antd";
import styled from 'styled-components'; import styled from "styled-components";
import { topEndpointListItem } from '../../actions/metrics'; import { topEndpointListItem } from "../../actions/metrics";
const Wrapper = styled.div` const Wrapper = styled.div`
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
.ant-table table { font-size: 12px; }; .ant-table table {
.ant-table tfoot>tr>td, .ant-table tfoot>tr>th, .ant-table-tbody>tr>td, .ant-table-thead>tr>th { padding: 10px; }; font-size: 12px;
}
.ant-table tfoot > tr > td,
.ant-table tfoot > tr > th,
.ant-table-tbody > tr > td,
.ant-table-thead > tr > th {
padding: 10px;
}
`; `;
interface TopEndpointsTableProps { interface TopEndpointsTableProps {
data : topEndpointListItem[], data: topEndpointListItem[];
} }
const TopEndpointsTable = (props: TopEndpointsTableProps) => { const TopEndpointsTable = (props: TopEndpointsTableProps) => {
const columns: any = [ const columns: any = [
{ {
title: 'Name', title: "Name",
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
render: (text :string) => <NavLink to={'/' + text}>{text}</NavLink>,
render: (text: string) => <NavLink to={"/" + text}>{text}</NavLink>,
}, },
{ {
title: 'P50 (in ms)', title: "P50 (in ms)",
dataIndex: 'p50', dataIndex: "p50",
key: 'p50', key: "p50",
sorter: (a: any, b: any) => a.p50 - b.p50, sorter: (a: any, b: any) => a.p50 - b.p50,
// sortDirections: ['descend', 'ascend'], // sortDirections: ['descend', 'ascend'],
render: (value: number) => (value / 1000000).toFixed(2), render: (value: number) => (value / 1000000).toFixed(2),
}, },
{ {
title: 'P90 (in ms)', title: "P90 (in ms)",
dataIndex: 'p90', dataIndex: "p90",
key: 'p90', key: "p90",
sorter: (a: any, b: any) => a.p90 - b.p90, sorter: (a: any, b: any) => a.p90 - b.p90,
// sortDirections: ['descend', 'ascend'], // sortDirections: ['descend', 'ascend'],
render: (value: number) => (value / 1000000).toFixed(2), render: (value: number) => (value / 1000000).toFixed(2),
}, },
{ {
title: 'P99 (in ms)', title: "P99 (in ms)",
dataIndex: 'p99', dataIndex: "p99",
key: 'p99', key: "p99",
sorter: (a: any, b: any) => a.p99 - b.p99, sorter: (a: any, b: any) => a.p99 - b.p99,
// sortDirections: ['descend', 'ascend'], // sortDirections: ['descend', 'ascend'],
render: (value: number) => (value / 1000000).toFixed(2), render: (value: number) => (value / 1000000).toFixed(2),
}, },
{ {
title: 'Number of Calls', title: "Number of Calls",
dataIndex: 'numCalls', dataIndex: "numCalls",
key: 'numCalls', key: "numCalls",
sorter: (a: any, b: any) => a.numCalls - b.numCalls, sorter: (a: any, b: any) => a.numCalls - b.numCalls,
}, },
]; ];
@ -67,7 +70,7 @@ const TopEndpointsTable = (props: TopEndpointsTableProps) => {
<h6> Top Endpoints</h6> <h6> Top Endpoints</h6>
<Table dataSource={props.data} columns={columns} pagination={false} /> <Table dataSource={props.data} columns={columns} pagination={false} />
</Wrapper> </Wrapper>
) );
} };
export default TopEndpointsTable; export default TopEndpointsTable;

View File

@ -11,7 +11,14 @@ import Graph from "react-graph-vis";
// https://github.com/crubier/react-graph-vis/issues/93 // https://github.com/crubier/react-graph-vis/issues/93
const graph = { const graph = {
nodes: [ nodes: [
{ id: 1, label: "Catalogue", shape: "box", color: "green",border: "black",size: 100 }, {
id: 1,
label: "Catalogue",
shape: "box",
color: "green",
border: "black",
size: 100,
},
{ id: 2, label: "Users", shape: "box", color: "#FFFF00" }, { id: 2, label: "Users", shape: "box", color: "#FFFF00" },
{ id: 3, label: "Payment App", shape: "box", color: "#FB7E81" }, { id: 3, label: "Payment App", shape: "box", color: "#FB7E81" },
{ id: 4, label: "My Sql", shape: "box", size: 10, color: "#7BE141" }, { 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: 1, to: 3, color: { color: "red" } },
{ from: 3, to: 4, color: { color: "red" } }, { from: 3, to: 4, color: { color: "red" } },
{ from: 3, to: 5, color: { color: "red" } }, { from: 3, to: 5, color: { color: "red" } },
] ],
}; };
const options = { const options = {
layout: { layout: {
hierarchical: true hierarchical: true,
}, },
edges: { edges: {
color: "#000000" color: "#000000",
}, },
height: "500px" height: "500px",
}; };
// const events = { // const events = {
@ -43,7 +50,6 @@ const graph = {
// }; // };
const ServiceGraph = () => { const ServiceGraph = () => {
// const [network, setNetwork] = useState(null); // const [network, setNetwork] = useState(null);
return ( 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 // // if you want access to vis.js network api you can set the state in a parent component using this property
// }} // }}
/> />
</React.Fragment> </React.Fragment>
); );
};
}
export default ServiceGraph; export default ServiceGraph;

View File

@ -1,19 +1,14 @@
import React from 'react'; import React from "react";
import ServiceGraph from './ServiceGraph' import ServiceGraph from "./ServiceGraph";
const ServiceMap = () => { const ServiceMap = () => {
return ( return (
<div>
<div> Service Map module coming soon... {" "}
Service Map module coming soon...
{/* <ServiceGraph /> */} {/* <ServiceGraph /> */}
</div> </div>
); );
};
}
export default ServiceMap; export default ServiceMap;

View File

@ -1,77 +1,115 @@
import React from 'react'; import React from "react";
import { Card, Tag } from 'antd'; import { Card, Tag } from "antd";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { StoreState } from "../../reducers";
import { StoreState } from '../../reducers' import { TagItem, TraceFilters, updateTraceFilters } from "../../actions";
import { TagItem, TraceFilters, updateTraceFilters } from '../../actions';
interface FilterStateDisplayProps { interface FilterStateDisplayProps {
traceFilters: TraceFilters, traceFilters: TraceFilters;
updateTraceFilters: Function, updateTraceFilters: Function;
} }
const _FilterStateDisplay = (props: FilterStateDisplayProps) => { const _FilterStateDisplay = (props: FilterStateDisplayProps) => {
function handleCloseTag(value: string) { function handleCloseTag(value: string) {
if (value==='service') if (value === "service")
props.updateTraceFilters({...props.traceFilters,service:''}) props.updateTraceFilters({ ...props.traceFilters, service: "" });
if (value==='operation') if (value === "operation")
props.updateTraceFilters({...props.traceFilters,operation:''}) props.updateTraceFilters({ ...props.traceFilters, operation: "" });
if (value==='maxLatency') if (value === "maxLatency")
props.updateTraceFilters({...props.traceFilters,latency:{'max':'','min':props.traceFilters.latency?.min}}) props.updateTraceFilters({
if (value==='minLatency') ...props.traceFilters,
props.updateTraceFilters({...props.traceFilters,latency:{'min':'','max':props.traceFilters.latency?.max}}) 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) { 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 ( return (
<Card
<Card style={{padding: 6, marginTop: 10, marginBottom: 10}} bodyStyle={{padding: 6}}> 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 {props.traceFilters.service === "" ||
onClose={e => {handleCloseTag('service');}}> props.traceFilters.operation === undefined ? null : (
<Tag
style={{ fontSize: 14, padding: 8 }}
closable
onClose={(e) => {
handleCloseTag("service");
}}
>
service:{props.traceFilters.service} service:{props.traceFilters.service}
</Tag> } </Tag>
{(props.traceFilters.operation===''||props.traceFilters.operation===undefined)? null: )}
<Tag style={{fontSize:14, padding: 8}} closable {props.traceFilters.operation === "" ||
onClose={e => {handleCloseTag('operation');}}> props.traceFilters.operation === undefined ? null : (
<Tag
style={{ fontSize: 14, padding: 8 }}
closable
onClose={(e) => {
handleCloseTag("operation");
}}
>
operation:{props.traceFilters.operation} operation:{props.traceFilters.operation}
</Tag> } </Tag>
{props.traceFilters.latency===undefined||props.traceFilters.latency?.min===''? null: )}
<Tag style={{fontSize:14, padding: 8}} closable {props.traceFilters.latency === undefined ||
onClose={e => {handleCloseTag('minLatency');}}> props.traceFilters.latency?.min === "" ? null : (
minLatency:{(parseInt(props.traceFilters.latency!.min)/1000000).toString()}ms <Tag
</Tag> } style={{ fontSize: 14, padding: 8 }}
{props.traceFilters.latency===undefined||props.traceFilters.latency?.max===''? null: closable
<Tag style={{fontSize:14, padding: 8}} closable onClose={(e) => {
onClose={e => {handleCloseTag('maxLatency');}}> handleCloseTag("minLatency");
maxLatency:{(parseInt(props.traceFilters.latency!.max)/1000000).toString()}ms }}
</Tag> } >
{props.traceFilters.tags === undefined? null: props.traceFilters.tags.map( item => ( minLatency:
<Tag style={{fontSize:14, padding: 8}} closable {(parseInt(props.traceFilters.latency!.min) / 1000000).toString()}ms
onClose={e => {handleCloseTagElement(item);}}> </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} {item.key} {item.operator} {item.value}
</Tag>))} </Tag>
))}
</Card> </Card>
); );
} };
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters } => { const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters } => {
return { traceFilters: state.traceFilters }; return { traceFilters: state.traceFilters };
}; };
export const FilterStateDisplay = connect(mapStateToProps, {
export const FilterStateDisplay = connect(mapStateToProps,
{
updateTraceFilters: updateTraceFilters, updateTraceFilters: updateTraceFilters,
})(_FilterStateDisplay); })(_FilterStateDisplay);

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from "react";
import { Modal, Form, InputNumber, Col, Row} from 'antd'; import { Modal, Form, InputNumber, Col, Row } from "antd";
import { Store } from 'antd/lib/form/interface'; import { Store } from "antd/lib/form/interface";
interface LatencyModalFormProps { interface LatencyModalFormProps {
visible: boolean; visible: boolean;
@ -25,12 +24,12 @@ const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
onOk={() => { onOk={() => {
form form
.validateFields() .validateFields()
.then(values => { .then((values) => {
form.resetFields(); form.resetFields();
onCreate(values); // giving error for values onCreate(values); // giving error for values
}) })
.catch(info => { .catch((info) => {
console.log('Validate Failed:', info); console.log("Validate Failed:", info);
}); });
}} }}
> >
@ -38,7 +37,7 @@ const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
form={form} form={form}
layout="horizontal" layout="horizontal"
name="form_in_modal" name="form_in_modal"
initialValues={{ min: '100', max:'500' }} initialValues={{ min: "100", max: "500" }}
> >
<Row> <Row>
{/* <Input.Group compact> */} {/* <Input.Group compact> */}

View File

@ -1,9 +1,7 @@
import React from 'react'; import React from "react";
import {Card,Tabs} from 'antd'; import { Card, Tabs } from "antd";
const { TabPane } = Tabs; const { TabPane } = Tabs;
interface spanTagItem { interface spanTagItem {
key: string; key: string;
type: string; type: string;
@ -11,36 +9,44 @@ interface spanTagItem {
} }
interface SelectedSpanDetailsProps { interface SelectedSpanDetailsProps {
clickedSpanTags: spanTagItem[] clickedSpanTags: spanTagItem[];
} }
const SelectedSpanDetails = (props: SelectedSpanDetailsProps) => { const SelectedSpanDetails = (props: SelectedSpanDetailsProps) => {
const callback = (key: any) => {};
const callback = (key:any) => {
}
return ( return (
<Card style={{ height: 320 }}> <Card style={{ height: 320 }}>
<Tabs defaultActiveKey="1" onChange={callback}> <Tabs defaultActiveKey="1" onChange={callback}>
<TabPane tab="Tags" key="1"> <TabPane tab="Tags" key="1">
<strong> Details for selected Span </strong> <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> {props.clickedSpanTags.map((tags, index) => (
<TabPane tab="Errors" key="2"> <li
{props.clickedSpanTags.filter(tags=>tags.key==='error').map( key={index}
error => <div className='ml-5'> style={{ color: "grey", fontSize: "13px", listStyle: "none" }}
<p style={{color:'grey',fontSize:'10px'}}> >
<span className='mr-1'>{error.key}</span>: <span className="mr-1">{tags.key}</span>:
<span className='ml-1'>true</span></p> <span className="ml-1">
</div>)} {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> </TabPane>
</Tabs> </Tabs>
</Card> </Card>
); );
};
}
export default SelectedSpanDetails; export default SelectedSpanDetails;

View File

@ -1,95 +1,112 @@
import React, {useState,useEffect} from 'react'; import React, { useState, useEffect } from "react";
import GenericVisualizations from '../metrics/GenericVisualization' import GenericVisualizations from "../metrics/GenericVisualization";
import {Select, Card, Space, Form} from 'antd'; import { Select, Card, Space, Form } from "antd";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { StoreState } from '../../reducers' import { StoreState } from "../../reducers";
import {customMetricsItem, getFilteredTraceMetrics, GlobalTime, TraceFilters} from '../../actions'; import {
customMetricsItem,
getFilteredTraceMetrics,
GlobalTime,
TraceFilters,
} from "../../actions";
const { Option } = Select; const { Option } = Select;
const entity = [ const entity = [
{ {
title: 'Calls', title: "Calls",
key:'calls', key: "calls",
dataindex:'calls' dataindex: "calls",
}, },
{ {
title: 'Duration', title: "Duration",
key:'duration', key: "duration",
dataindex:'duration' dataindex: "duration",
}, },
{ {
title: 'Error', title: "Error",
key:'error', key: "error",
dataindex:'error' dataindex: "error",
}, },
{ {
title: 'Status Code', title: "Status Code",
key:'status_code', key: "status_code",
dataindex:'status_code' dataindex: "status_code",
}, },
]; ];
const aggregation_options = [ const aggregation_options = [
{ {
linked_entity: 'calls', linked_entity: "calls",
default_selected:{title:'Count', dataindex:'count'}, default_selected: { title: "Count", dataindex: "count" },
options_available: [ {title:'Count', dataindex:'count'}, {title:'Rate (per sec)', dataindex:'rate_per_sec'}] options_available: [
{ title: "Count", dataindex: "count" },
{ title: "Rate (per sec)", dataindex: "rate_per_sec" },
],
}, },
{ {
linked_entity: 'duration', linked_entity: "duration",
default_selected:{title:'p99', dataindex:'p99'}, 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:'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', linked_entity: "error",
default_selected:{title:'Count', dataindex:'count'}, default_selected: { title: "Count", dataindex: "count" },
options_available: [ {title:'Count', dataindex:'count'}, {title:'Rate (per sec)', dataindex:'rate_per_sec'}] options_available: [
{ title: "Count", dataindex: "count" },
{ title: "Rate (per sec)", dataindex: "rate_per_sec" },
],
}, },
{ {
linked_entity: 'status_code', linked_entity: "status_code",
default_selected: {title:'Count', dataindex:'count'}, default_selected: { title: "Count", dataindex: "count" },
options_available: [ {title:'Count', dataindex:'count'}] options_available: [{ title: "Count", dataindex: "count" }],
}, },
]; ];
interface TraceCustomVisualizationsProps { interface TraceCustomVisualizationsProps {
filteredTraceMetrics: customMetricsItem[], filteredTraceMetrics: customMetricsItem[];
globalTime: GlobalTime, globalTime: GlobalTime;
getFilteredTraceMetrics: Function, getFilteredTraceMetrics: Function;
traceFilters: TraceFilters, traceFilters: TraceFilters;
} }
const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => { const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
const [selectedEntity, setSelectedEntity] = useState("calls");
const [selectedEntity, setSelectedEntity] = useState('calls'); const [selectedAggOption, setSelectedAggOption] = useState("count");
const [selectedAggOption, setSelectedAggOption] = useState('count'); const [selectedStep, setSelectedStep] = useState("60");
const [selectedStep, setSelectedStep] = useState('60');
// Step should be multiples of 60, 60 -> 1 min // Step should be multiples of 60, 60 -> 1 min
useEffect(() => { useEffect(() => {
let request_string =
let request_string= 'service='+props.traceFilters.service+ "service=" +
'&operation='+props.traceFilters.operation+ props.traceFilters.service +
'&maxDuration='+props.traceFilters.latency?.max+ "&operation=" +
'&minDuration='+props.traceFilters.latency?.min props.traceFilters.operation +
"&maxDuration=" +
props.traceFilters.latency?.max +
"&minDuration=" +
props.traceFilters.latency?.min;
if (props.traceFilters.tags) 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) if (selectedEntity)
request_string=request_string+'&dimension='+selectedEntity.toLowerCase(); request_string =
request_string + "&dimension=" + selectedEntity.toLowerCase();
if (selectedAggOption) if (selectedAggOption)
request_string=request_string+'&aggregation_option='+selectedAggOption.toLowerCase(); request_string =
if(selectedStep) request_string + "&aggregation_option=" + selectedAggOption.toLowerCase();
request_string=request_string+'&step='+selectedStep; if (selectedStep) request_string = request_string + "&step=" + selectedStep;
props.getFilteredTraceMetrics(request_string, props.globalTime)
props.getFilteredTraceMetrics(request_string, props.globalTime);
}, [selectedEntity, selectedAggOption, props.traceFilters, props.globalTime]); }, [selectedEntity, selectedAggOption, props.traceFilters, props.globalTime]);
//Custom metrics API called if time, tracefilters, selected entity or agg option changes //Custom metrics API called if time, tracefilters, selected entity or agg option changes
@ -104,74 +121,68 @@ const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
// console.log(value); // console.log(value);
} }
// PNOTE - Can also use 'coordinate' option in antd Select for implementing this - https://ant.design/components/select/ // PNOTE - Can also use 'coordinate' option in antd Select for implementing this - https://ant.design/components/select/
const handleFormValuesChange = (changedValues: any) => { const handleFormValuesChange = (changedValues: any) => {
const formFieldName = Object.keys(changedValues)[0]; const formFieldName = Object.keys(changedValues)[0];
if (formFieldName === 'entity') { if (formFieldName === "entity") {
const temp_entity = aggregation_options.filter(
const temp_entity = aggregation_options.filter((item) => item.linked_entity === changedValues[formFieldName])[0]; (item) => item.linked_entity === changedValues[formFieldName],
)[0];
form.setFieldsValue({ form.setFieldsValue({
agg_options: temp_entity.default_selected.title, agg_options: temp_entity.default_selected.title,
// PNOTE - TO DO Check if this has the same behaviour as selecting an option? // 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); setSelectedEntity(temp.entity);
setSelectedAggOption(temp.agg_options); setSelectedAggOption(temp.agg_options);
//form.setFieldsValue({ agg_options: aggregation_options.filter( item => item.linked_entity === selectedEntity )[0] }); //reset product selection //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 // 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]); setSelectedAggOption(changedValues[formFieldName]);
} }
};
}
return ( return (
<Card> <Card>
{/* <Space direction="vertical"> */} {/* <Space direction="vertical"> */}
<div>Custom Visualizations</div> <div>Custom Visualizations</div>
<Form <Form
form={form} form={form}
onFinish={handleFinish} onFinish={handleFinish}
onValuesChange={handleFormValuesChange} 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> <Space>
<Form.Item name="entity"> <Form.Item name="entity">
<Select defaultValue={selectedEntity} style={{ width: 120 }} allowClear> <Select defaultValue={selectedEntity} style={{ width: 120 }} allowClear>
{entity.map((item) => ( {entity.map((item) => (
<Option key={item.key} value={item.dataindex}> <Option key={item.key} value={item.dataindex}>
{item.title} {item.title}
</Option> </Option>
) ))}
)
}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item name="agg_options"> <Form.Item name="agg_options">
<Select style={{ width: 120 }} allowClear> <Select style={{ width: 120 }} allowClear>
{ aggregation_options.filter((item) => item.linked_entity === selectedEntity)[0].options_available {aggregation_options
.map((item) => ( .filter((item) => item.linked_entity === selectedEntity)[0]
.options_available.map((item) => (
<Option key={item.dataindex} value={item.dataindex}> <Option key={item.dataindex} value={item.dataindex}>
{item.title} {item.title}
</Option> </Option>
)) ))}
}
</Select> </Select>
</Form.Item> </Form.Item>
@ -202,19 +213,25 @@ const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
</Space> </Space>
</Form> </Form>
<GenericVisualizations chartType="line" data={props.filteredTraceMetrics} />
<GenericVisualizations chartType='line' data={props.filteredTraceMetrics}/>
{/* This component should take bar or line as an input */} {/* This component should take bar or line as an input */}
</Card> </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, { export const TraceCustomVisualizations = connect(mapStateToProps, {
getFilteredTraceMetrics: getFilteredTraceMetrics, getFilteredTraceMetrics: getFilteredTraceMetrics,

View File

@ -1,22 +1,16 @@
import React from 'react'; import React from "react";
import {TraceCustomVisualizations} from './TraceCustomVisualizations'; import { TraceCustomVisualizations } from "./TraceCustomVisualizations";
import { TraceFilter } from './TraceFilter'; import { TraceFilter } from "./TraceFilter";
import { TraceList } from './TraceList'; import { TraceList } from "./TraceList";
const TraceDetail = () => { const TraceDetail = () => {
return ( return (
<div> <div>
<TraceFilter /> <TraceFilter />
<TraceCustomVisualizations /> <TraceCustomVisualizations />
<TraceList /> <TraceList />
</div> </div>
); );
};
}
export default TraceDetail; export default TraceDetail;

View File

@ -1,18 +1,21 @@
import React,{useEffect, useState} from 'react'; import React, { useEffect, useState } from "react";
import { Select, Button, Input, Form, AutoComplete} from 'antd'; import { Select, Button, Input, Form, AutoComplete } from "antd";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { Store } from 'antd/lib/form/interface'; import { Store } from "antd/lib/form/interface";
import styled from 'styled-components'; import styled from "styled-components";
import { updateTraceFilters, fetchTraces, TraceFilters, GlobalTime } from '../../actions'; import {
import { StoreState } from '../../reducers'; updateTraceFilters,
import LatencyModalForm from './LatencyModalForm'; fetchTraces,
import {FilterStateDisplay} from './FilterStateDisplay'; TraceFilters,
GlobalTime,
} from "../../actions";
import FormItem from 'antd/lib/form/FormItem'; import { StoreState } from "../../reducers";
import metricsAPI from '../../api/metricsAPI'; import LatencyModalForm from "./LatencyModalForm";
import { FilterStateDisplay } from "./FilterStateDisplay";
import FormItem from "antd/lib/form/FormItem";
import metricsAPI from "../../api/metricsAPI";
const { Option } = Select; const { Option } = Select;
@ -23,74 +26,95 @@ font-size: 12px;
`; `;
interface TraceFilterProps { interface TraceFilterProps {
traceFilters: TraceFilters, traceFilters: TraceFilters;
globalTime: GlobalTime, globalTime: GlobalTime;
updateTraceFilters: Function, updateTraceFilters: Function;
fetchTraces: Function, fetchTraces: Function;
} }
interface TagKeyOptionItem { interface TagKeyOptionItem {
"tagKeys": string; tagKeys: string;
"tagCount": number; tagCount: number;
} }
const _TraceFilter = (props: TraceFilterProps) => { const _TraceFilter = (props: TraceFilterProps) => {
const [serviceList, setServiceList] = useState<string[]>([]); const [serviceList, setServiceList] = useState<string[]>([]);
const [operationList, setOperationsList] = useState<string[]>([]); const [operationList, setOperationsList] = useState<string[]>([]);
const [tagKeyOptions, setTagKeyOptions] = useState<TagKeyOptionItem[]>([]); const [tagKeyOptions, setTagKeyOptions] = useState<TagKeyOptionItem[]>([]);
useEffect(() => { useEffect(() => {
metricsAPI.get<string[]>('services/list').then(response => { metricsAPI.get<string[]>("services/list").then((response) => {
setServiceList(response.data); setServiceList(response.data);
}); });
}, []); }, []);
useEffect(() => { useEffect(() => {
let request_string='service='+props.traceFilters.service+ let request_string =
'&operation='+props.traceFilters.operation+ "service=" +
'&maxDuration='+props.traceFilters.latency?.max+ props.traceFilters.service +
'&minDuration='+props.traceFilters.latency?.min "&operation=" +
props.traceFilters.operation +
"&maxDuration=" +
props.traceFilters.latency?.max +
"&minDuration=" +
props.traceFilters.latency?.min;
if (props.traceFilters.tags) 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]); }, [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(() => { useEffect(() => {
form_basefilter.setFieldsValue({ service: props.traceFilters.service });
let latencyButtonText = 'Latency'; }, [props.traceFilters.service]);
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(() => { useEffect(() => {
form_basefilter.setFieldsValue({ operation: props.traceFilters.operation });
form_basefilter.setFieldsValue({service: props.traceFilters.service,}) }, [props.traceFilters.operation]);
}, [props.traceFilters.service])
useEffect ( () => {
form_basefilter.setFieldsValue({operation: props.traceFilters.operation,})
}, [props.traceFilters.operation])
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const [loading] = useState(false); const [loading] = useState(false);
const [tagKeyValueApplied, setTagKeyValueApplied]=useState(['']); const [tagKeyValueApplied, setTagKeyValueApplied] = useState([""]);
const [latencyFilterValues, setLatencyFilterValues]=useState({min:'',max:''}) const [latencyFilterValues, setLatencyFilterValues] = useState({
min: "",
max: "",
});
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -101,68 +125,88 @@ const _TraceFilter = (props: TraceFilterProps) => {
} }
function handleChangeOperation(value: string) { function handleChangeOperation(value: string) {
props.updateTraceFilters({...props.traceFilters,operation:value}) props.updateTraceFilters({ ...props.traceFilters, operation: value });
} }
function handleChangeService(value: string) { function handleChangeService(value: string) {
let service_request='/service/'+value+'/operations'; let service_request = "/service/" + value + "/operations";
metricsAPI.get<string[]>(service_request).then(response => { metricsAPI.get<string[]>(service_request).then((response) => {
// form_basefilter.resetFields(['operation',]) // form_basefilter.resetFields(['operation',])
setOperationsList(response.data); setOperationsList(response.data);
}); });
let tagkeyoptions_request='tags?service='+value; let tagkeyoptions_request = "tags?service=" + value;
metricsAPI.get<TagKeyOptionItem[]>(tagkeyoptions_request).then(response => { metricsAPI.get<TagKeyOptionItem[]>(tagkeyoptions_request).then((response) => {
setTagKeyOptions(response.data); setTagKeyOptions(response.data);
}); });
props.updateTraceFilters({...props.traceFilters,service:value}) props.updateTraceFilters({ ...props.traceFilters, service: value });
} }
const onLatencyButtonClick = () => { const onLatencyButtonClick = () => {
setModalVisible(true); setModalVisible(true);
} };
const onLatencyModalApply = (values: Store) => { const onLatencyModalApply = (values: Store) => {
setModalVisible(false); 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) => { const onTagFormSubmit = (values: any) => {
let request_tags =
let request_tags= 'service=frontend&tags='+encodeURIComponent(JSON.stringify([{"key":values.tag_key,"value":values.tag_value,"operator":values.operator}])) "service=frontend&tags=" +
encodeURIComponent(
JSON.stringify([
if (props.traceFilters.tags){ // If there are existing tag filters present
props.updateTraceFilters(
{ {
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, service: props.traceFilters.service,
operation: props.traceFilters.operation, operation: props.traceFilters.operation,
latency: props.traceFilters.latency, 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 {
else props.updateTraceFilters({
{
props.updateTraceFilters(
{
service: props.traceFilters.service, service: props.traceFilters.service,
operation: props.traceFilters.operation, operation: props.traceFilters.operation,
latency: props.traceFilters.latency, 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(); form.resetFields();
} };
const onTagClose = (value: string) => { const onTagClose = (value: string) => {
setTagKeyValueApplied(tagKeyValueApplied.filter( e => (e !== value))); setTagKeyValueApplied(tagKeyValueApplied.filter((e) => e !== value));
};
}
// For autocomplete // For autocomplete
//Setting value when autocomplete field is changed //Setting value when autocomplete field is changed
@ -170,59 +214,104 @@ const _TraceFilter = (props: TraceFilterProps) => {
form.setFieldsValue({ tag_key: data }); form.setFieldsValue({ tag_key: data });
}; };
const dataSource = ['status:200']; const dataSource = ["status:200"];
const children = []; const children = [];
for (let i = 0; i < dataSource.length; i++) { 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 // PNOTE - Remove any
const handleApplyFilterForm = (values: any) => { const handleApplyFilterForm = (values: any) => {
let request_params: string = "";
let request_params: string =''; if (
if (typeof values.service !== undefined && typeof(values.operation) !== undefined) typeof values.service !== undefined &&
{ typeof values.operation !== undefined
request_params = 'service='+values.service+'&operation='+values.operation; ) {
} request_params =
else if (typeof values.service === undefined && typeof values.operation !== undefined) "service=" + values.service + "&operation=" + values.operation;
{ } else if (
request_params = 'operation='+values.operation; typeof values.service === undefined &&
} typeof values.operation !== undefined
else if (typeof values.service !== undefined && typeof values.operation === undefined) ) {
{ request_params = "operation=" + values.operation;
request_params = 'service='+values.service; } 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; request_params =
request_params +
"&minDuration=" +
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()]); latencyFilterValues.min +
props.updateTraceFilters({'service':values.service,'operation':values.operation,'latency':latencyFilterValues}) "&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 ( return (
<div> <div>
<div>Filter Traces</div> <div>Filter Traces</div>
{/* <div>{JSON.stringify(props.traceFilters)}</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}}> <Form
<FormItem rules={[{ required: true }]} name='service'> form={form_basefilter}
<Select showSearch style={{ width: 180 }} onChange={handleChangeService} placeholder='Select Service' allowClear> layout="inline"
{serviceList.map( s => <Option value={s}>{s}</Option>)} 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> </Select>
</FormItem> </FormItem>
<FormItem name='operation'> <FormItem name="operation">
<Select showSearch style={{ width: 180 }} onChange={handleChangeOperation} placeholder='Select Operation' allowClear> <Select
{operationList.map( item => <Option value={item}>{item}</Option>)} showSearch
style={{ width: 180 }}
onChange={handleChangeOperation}
placeholder="Select Operation"
allowClear
>
{operationList.map((item) => (
<Option value={item}>{item}</Option>
))}
</Select> </Select>
</FormItem> </FormItem>
<FormItem name='latency'> <FormItem name="latency">
<Input style={{ width: 200 }} type='button' onClick={onLatencyButtonClick}/> <Input
style={{ width: 200 }}
type="button"
onClick={onLatencyButtonClick}
/>
</FormItem> </FormItem>
{/* <FormItem> {/* <FormItem>
@ -232,19 +321,23 @@ const _TraceFilter = (props: TraceFilterProps) => {
<FilterStateDisplay /> <FilterStateDisplay />
{/* // What will be the empty state of card when there is no Tag , it should show something */} {/* // What will be the empty state of card when there is no Tag , it should show something */}
<InfoWrapper>Select Service to get Tag suggestions </InfoWrapper> <InfoWrapper>Select Service to get Tag suggestions </InfoWrapper>
<Form form={form} layout='inline' onFinish={onTagFormSubmit} initialValues={{operator:'equals'}} style={{marginTop: 10, marginBottom:10}}> <Form
form={form}
<FormItem rules={[{ required: true }]} name='tag_key'> layout="inline"
onFinish={onTagFormSubmit}
initialValues={{ operator: "equals" }}
style={{ marginTop: 10, marginBottom: 10 }}
>
<FormItem rules={[{ required: true }]} name="tag_key">
<AutoComplete <AutoComplete
options={tagKeyOptions.map((s) => { return ({'value' : s.tagKeys}) })} options={tagKeyOptions.map((s) => {
style={{ width: 200, textAlign: 'center' }} return { value: s.tagKeys };
})}
style={{ width: 200, textAlign: "center" }}
// onSelect={onSelect} // onSelect={onSelect}
// onSearch={onSearch} // onSearch={onSearch}
onChange={onChangeTagKey} onChange={onChangeTagKey}
@ -255,21 +348,26 @@ const _TraceFilter = (props: TraceFilterProps) => {
/> />
</FormItem> </FormItem>
<FormItem name='operator'> <FormItem name="operator">
<Select style={{ width: 120, textAlign: 'center' }}> <Select style={{ width: 120, textAlign: "center" }}>
<Option value="equals">EQUAL</Option> <Option value="equals">EQUAL</Option>
<Option value="contains">CONTAINS</Option> <Option value="contains">CONTAINS</Option>
</Select> </Select>
</FormItem> </FormItem>
<FormItem rules={[{ required: true }]} name='tag_value'> <FormItem rules={[{ required: true }]} name="tag_value">
<Input style={{ width: 160, textAlign: 'center',}} placeholder="Tag Value" /> <Input
style={{ width: 160, textAlign: "center" }}
placeholder="Tag Value"
/>
</FormItem> </FormItem>
<FormItem> <FormItem>
<Button type="primary" htmlType="submit"> Apply Tag Filter </Button> <Button type="primary" htmlType="submit">
{" "}
Apply Tag Filter{" "}
</Button>
</FormItem> </FormItem>
</Form> </Form>
<LatencyModalForm <LatencyModalForm
@ -281,9 +379,11 @@ const _TraceFilter = (props: TraceFilterProps) => {
/> />
</div> </div>
); );
} };
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters, globalTime: GlobalTime } => { const mapStateToProps = (
state: StoreState,
): { traceFilters: TraceFilters; globalTime: GlobalTime } => {
return { traceFilters: state.traceFilters, globalTime: state.globalTime }; return { traceFilters: state.traceFilters, globalTime: state.globalTime };
}; };

View File

@ -1,107 +1,104 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { flamegraph } from 'd3-flame-graph' import { flamegraph } from "d3-flame-graph";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { Card, Button, Row, Col, Space } from 'antd'; import { Card, Button, Row, Col, Space } from "antd";
import * as d3 from 'd3'; import * as d3 from "d3";
import * as d3Tip from 'd3-tip'; import * as d3Tip from "d3-tip";
//import * as d3Tip from 'd3-tip'; //import * as d3Tip from 'd3-tip';
// PNOTE - uninstall @types/d3-tip. issues with importing d3-tip https://github.com/Caged/d3-tip/issues/181 // PNOTE - uninstall @types/d3-tip. issues with importing d3-tip https://github.com/Caged/d3-tip/issues/181
import './TraceGraph.css' import "./TraceGraph.css";
import { spanToTreeUtil } from '../../utils/spanToTree' import { spanToTreeUtil } from "../../utils/spanToTree";
import { fetchTraceItem , spansWSameTraceIDResponse } from '../../actions'; import { fetchTraceItem, spansWSameTraceIDResponse } from "../../actions";
import { StoreState } from '../../reducers' import { StoreState } from "../../reducers";
import { TraceGraphColumn } from './TraceGraphColumn' import { TraceGraphColumn } from "./TraceGraphColumn";
import SelectedSpanDetails from './SelectedSpanDetails' import SelectedSpanDetails from "./SelectedSpanDetails";
interface TraceGraphProps { interface TraceGraphProps {
traceItem: spansWSameTraceIDResponse;
traceItem: spansWSameTraceIDResponse , fetchTraceItem: Function;
fetchTraceItem: Function,
} }
const _TraceGraph = (props: TraceGraphProps) => { const _TraceGraph = (props: TraceGraphProps) => {
const params = useParams<{ id?: string }>();
const params = useParams<{ id?: string; }>(); const [clickedSpanTags, setClickedSpanTags] = useState([]);
const [clickedSpanTags,setClickedSpanTags]=useState([]) const [resetZoom, setResetZoom] = useState(false);
useEffect(() => { useEffect(() => {
//sets span width based on value - which is mapped to duration
props.fetchTraceItem(params.id); props.fetchTraceItem(params.id);
}, []); }, []);
useEffect(() => { useEffect(() => {
if (props.traceItem[0].events.length>0) if (props.traceItem || resetZoom) {
{
const tree = spanToTreeUtil(props.traceItem[0].events); 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); 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 // 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 // Doesn't work if only do initial check, works if monitor an element - as it may get updated in sometime
const tip = d3Tip
.default()
const tip = d3Tip.default().attr('class', 'd3-tip').html(function(d:any) { return d.data.name+'<br>duration: '+d.data.value}); .attr("class", "d3-tip")
.html(function (d: any) {
return d.data.name + "<br>duration: " + d.data.value;
});
const onClick = (z: any) => { const onClick = (z: any) => {
setClickedSpanTags(z.data.tags); setClickedSpanTags(z.data.tags);
console.log(`Clicked on ${z.data.name}, id: "${z.id}"`); console.log(`Clicked on ${z.data.name}, id: "${z.id}"`);
} };
const chart = flamegraph() const chart = flamegraph()
.width(640) .width(640)
.cellHeight(18) .cellHeight(18)
.transitionDuration(500) .transitionDuration(500)
.minFrameSize(5)
.sort(true)
.inverted(true) .inverted(true)
.tooltip(tip) .tooltip(tip)
.minFrameSize(10)
.elided(false) .elided(false)
.onClick(onClick)
// .title("Trace Flame graph")
.differential(false) .differential(false)
.selfValue(true); //sets span width based on value - which is mapped to duration .sort(true)
//Use self value=true when we're using not using aggregated option, Which is not our case.
const resetZoom = () => { // In that case it's doing step function sort of stuff thru computation.
chart.resetZoom(); // Source flamegraph.js line 557 and 573.
} // .selfValue(true)
.onClick(onClick)
.title("Trace Flame graph");
return ( return (
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}> <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
<Col md={8} sm={24}> <Col md={8} sm={24}>
<TraceGraphColumn /> <TraceGraphColumn />
</Col> </Col>
<Col md={16} sm={24}> <Col md={16} sm={24}>
{/* <Card style={{ width: 640 }}> */} {/* <Card style={{ width: 640 }}> */}
<Space direction="vertical" size='middle' > <Space direction="vertical" size="middle">
<Card bodyStyle={{ padding: 80 }} style={{ height: 320 }}>
<Card bodyStyle={{padding: 80, }} style={{ height: 320, }}>
<div>Trace Graph component ID is {params.id} </div> <div>Trace Graph component ID is {params.id} </div>
<Button type="primary" onClick={resetZoom}>Reset Zoom</Button> <Button type="primary" onClick={setResetZoom.bind(this, true)}>
<div id="chart" style={{ fontSize: 12 }}></div> Reset Zoom
</Button>
<div id="chart" style={{ fontSize: 12, marginTop: 20}}></div>
</Card> </Card>
<SelectedSpanDetails clickedSpanTags={clickedSpanTags} /> <SelectedSpanDetails clickedSpanTags={clickedSpanTags} />
</Space> </Space>
</Col> </Col>
</Row> </Row>
); );
}
const mapStateToProps = (state: StoreState): { traceItem: spansWSameTraceIDResponse } => {
return { traceItem: state.traceItem };
}; };
const mapStateToProps = (
state: StoreState,
): { traceItem: spansWSameTraceIDResponse } => {
return { traceItem: state.traceItem };
};
export const TraceGraph = connect(mapStateToProps, { export const TraceGraph = connect(mapStateToProps, {
fetchTraceItem: fetchTraceItem, fetchTraceItem: fetchTraceItem,

View File

@ -1,13 +1,12 @@
import React from 'react'; import React from "react";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { Table } from 'antd' import { Table } from "antd";
import { traceResponseNew, pushDStree } from '../../actions';
import { StoreState } from '../../reducers'
import { traceResponseNew, pushDStree } from "../../actions";
import { StoreState } from "../../reducers";
interface TraceGraphColumnProps { interface TraceGraphColumnProps {
traces: traceResponseNew, traces: traceResponseNew;
} }
interface TableDataSourceItem { interface TableDataSourceItem {
@ -17,57 +16,62 @@ interface TableDataSourceItem {
duration: number; duration: number;
} }
const _TraceGraphColumn = (props: TraceGraphColumnProps) => { const _TraceGraphColumn = (props: TraceGraphColumnProps) => {
const columns: any = [ const columns: any = [
{ {
title: 'Start Time (UTC Time)', title: "Start Time (UTC Time)",
dataIndex: 'startTime', dataIndex: "startTime",
key: 'startTime', key: "startTime",
sorter: (a: any, b: any) => a.startTime - b.startTime, sorter: (a: any, b: any) => a.startTime - b.startTime,
sortDirections: ['descend', 'ascend'], sortDirections: ["descend", "ascend"],
render: (value: number) => (new Date(Math.round(value/1000))).toUTCString() render: (value: number) => new Date(Math.round(value / 1000)).toUTCString(),
}, },
{ {
title: 'Duration (in ms)', title: "Duration (in ms)",
dataIndex: 'duration', dataIndex: "duration",
key: 'duration', key: "duration",
sorter: (a: any, b: any) => a.duration - b.duration, sorter: (a: any, b: any) => a.duration - b.duration,
sortDirections: ['descend', 'ascend'], sortDirections: ["descend", "ascend"],
render: (value: number) => (value / 1000000).toFixed(2), render: (value: number) => (value / 1000000).toFixed(2),
}, },
{ {
title: 'Operation', title: "Operation",
dataIndex: 'operationName', dataIndex: "operationName",
key: 'operationName', key: "operationName",
}, },
]; ];
let dataSource: TableDataSourceItem[] = []; let dataSource: TableDataSourceItem[] = [];
if (props.traces[0].events.length > 0) { if (props.traces[0].events.length > 0) {
props.traces[0].events.map(
props.traces[0].events.map((item: (number|string|string[]|pushDStree[])[], index ) => { (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' ) if (
dataSource.push({startTime: item[0], operationName: item[4] , duration:parseInt(item[6]), key:index.toString()}); 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 ( return (
<div> <div>
<Table dataSource={dataSource} columns={columns} size="middle" />; <Table dataSource={dataSource} columns={columns} size="middle" />;
</div> </div>
); );
};
}
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => { const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
return { traces: state.traces }; return { traces: state.traces };
}; };
export const TraceGraphColumn = connect(mapStateToProps)(_TraceGraphColumn); export const TraceGraphColumn = connect(mapStateToProps)(_TraceGraphColumn);

View File

@ -1,4 +1,4 @@
export { TraceGraph as default } from './TraceGraph'; export { TraceGraph as default } from "./TraceGraph";
// PNOTE // PNOTE
// Because react.lazy doesn't work on named components // Because react.lazy doesn't work on named components

View File

@ -1,14 +1,14 @@
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { NavLink } from 'react-router-dom'; import { NavLink } from "react-router-dom";
import { Table } from 'antd' import { Table } from "antd";
import { traceResponseNew, fetchTraces, pushDStree } from '../../actions'; import { traceResponseNew, fetchTraces, pushDStree } from "../../actions";
import { StoreState } from '../../reducers' import { StoreState } from "../../reducers";
interface TraceListProps { interface TraceListProps {
traces: traceResponseNew, traces: traceResponseNew;
fetchTraces: Function, fetchTraces: Function;
} }
interface TableDataSourceItem { interface TableDataSourceItem {
@ -20,9 +20,7 @@ interface TableDataSourceItem {
duration: number; duration: number;
} }
const _TraceList = (props: TraceListProps) => { const _TraceList = (props: TraceListProps) => {
// PNOTE (TO DO) - Currently this use of useEffect gives warning. May need to memoise fetchtraces - https://stackoverflow.com/questions/55840294/how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook // PNOTE (TO DO) - Currently this use of useEffect gives warning. May need to memoise fetchtraces - https://stackoverflow.com/questions/55840294/how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook
useEffect(() => { useEffect(() => {
@ -42,60 +40,74 @@ const _TraceList = (props: TraceListProps) => {
const columns: any = [ const columns: any = [
{ {
title: 'Start Time (UTC Time)', title: "Start Time (UTC Time)",
dataIndex: 'startTime', dataIndex: "startTime",
key: 'startTime', key: "startTime",
sorter: (a: any, b: any) => a.startTime - b.startTime, sorter: (a: any, b: any) => a.startTime - b.startTime,
sortDirections: ['descend', 'ascend'], sortDirections: ["descend", "ascend"],
render: (value: number) => (new Date(Math.round(value))).toUTCString() render: (value: number) => new Date(Math.round(value)).toUTCString(),
// new Date() assumes input in milliseconds. Start Time stamp returned by druid api for span list is in ms // new Date() assumes input in milliseconds. Start Time stamp returned by druid api for span list is in ms
}, },
{ {
title: 'Duration (in ms)', title: "Duration (in ms)",
dataIndex: 'duration', dataIndex: "duration",
key: 'duration', key: "duration",
sorter: (a: any, b: any) => a.duration - b.duration, sorter: (a: any, b: any) => a.duration - b.duration,
sortDirections: ['descend', 'ascend'], sortDirections: ["descend", "ascend"],
render: (value: number) => (value / 1000000).toFixed(2), render: (value: number) => (value / 1000000).toFixed(2),
}, },
{ {
title: 'Operation', title: "Operation",
dataIndex: 'operationName', dataIndex: "operationName",
key: 'operationName', key: "operationName",
}, },
{ {
title: 'TraceID', title: "TraceID",
dataIndex: 'traceid', dataIndex: "traceid",
key: 'traceid', key: "traceid",
render: (text :string) => <NavLink to={'/traces/' + text}>{text.slice(-16)}</NavLink>, render: (text: string) => (
<NavLink to={"/traces/" + text}>{text.slice(-16)}</NavLink>
),
//only last 16 chars have traceID, druid makes it 32 by adding zeros //only last 16 chars have traceID, druid makes it 32 by adding zeros
}, },
]; ];
let dataSource: TableDataSourceItem[] = []; let dataSource: TableDataSourceItem[] = [];
const renderTraces = () => { const renderTraces = () => {
if (
if (typeof props.traces[0]!== 'undefined' && props.traces[0].events.length > 0) { 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 //PNOTE - Template literal should be wrapped in curly braces for it to be evaluated
props.traces[0].events.map(
props.traces[0].events.map((item: (number|string|string[]|pushDStree[])[], index ) => { (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' ) if (
dataSource.push({startTime: item[0], operationName: item[4] , duration:parseInt(item[6]), spanid:item[1], traceid:item[2], key:index.toString()}); 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 //antd table in typescript - https://codesandbox.io/s/react-typescript-669cv
return <Table dataSource={dataSource} columns={columns} size="middle" />; return <Table dataSource={dataSource} columns={columns} size="middle" />;
} else } else {
{ return <div> No spans found for given filter!</div>;
return <div> No spans found for given filter!</div>
} }
}; // end of renderTraces }; // end of renderTraces
return ( return (
@ -103,9 +115,8 @@ const _TraceList = (props: TraceListProps) => {
<div>List of traces with spanID</div> <div>List of traces with spanID</div>
<div>{renderTraces()}</div> <div>{renderTraces()}</div>
</div> </div>
) );
};
}
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => { const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
return { traces: state.traces }; return { traces: state.traces };

View File

@ -1,37 +1,34 @@
import React, {useEffect} from 'react'; import React, { useEffect } from "react";
import { Bar } from 'react-chartjs-2' import { Bar } from "react-chartjs-2";
import { Card } from 'antd' import { Card } from "antd";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { getUsageData, GlobalTime, usageDataItem } from '../../actions'; import { getUsageData, GlobalTime, usageDataItem } from "../../actions";
import { StoreState } from '../../reducers' import { StoreState } from "../../reducers";
interface UsageExplorerProps { interface UsageExplorerProps {
usageData: usageDataItem[], usageData: usageDataItem[];
getUsageData: Function, getUsageData: Function;
globalTime: GlobalTime, globalTime: GlobalTime;
} }
const _UsageExplorer = (props: UsageExplorerProps) => { const _UsageExplorer = (props: UsageExplorerProps) => {
useEffect(() => { useEffect(() => {
props.getUsageData(props.globalTime); props.getUsageData(props.globalTime);
}, [props.globalTime]); }, [props.globalTime]);
const data = { const data = {
labels: props.usageData.map(s => new Date(s.timestamp/1000000)), labels: props.usageData.map((s) => new Date(s.timestamp / 1000000)),
datasets: [ datasets: [
{ {
label: 'Span Count', label: "Span Count",
data: props.usageData.map(s => s.count), data: props.usageData.map((s) => s.count),
backgroundColor: 'rgba(255, 99, 132, 0.2)', backgroundColor: "rgba(255, 99, 132, 0.2)",
borderColor: 'rgba(255, 99, 132, 1)', borderColor: "rgba(255, 99, 132, 1)",
borderWidth: 2, borderWidth: 2,
}, },
], ],
} };
const options = { const options = {
scales: { scales: {
@ -45,7 +42,7 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
], ],
xAxes: [ xAxes: [
{ {
type: 'time', type: "time",
// distribution: 'linear', // Bar graph doesn't take lineardistribution type? // distribution: 'linear', // Bar graph doesn't take lineardistribution type?
ticks: { ticks: {
@ -57,8 +54,8 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
}, },
legend: { legend: {
display: false, display: false,
} },
} };
return ( return (
<React.Fragment> <React.Fragment>
@ -66,12 +63,13 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
<Card style={{ width: "50%", margin: 20 }} bodyStyle={{ padding: 20 }}> <Card style={{ width: "50%", margin: 20 }} bodyStyle={{ padding: 20 }}>
<Bar data={data} options={options} /> <Bar data={data} options={options} />
</Card> </Card>
</React.Fragment> </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 }; return { usageData: state.usageDate, globalTime: state.globalTime };
}; };

View File

@ -1 +1 @@
export { UsageExplorer as default } from './UsageExplorer'; export { UsageExplorer as default } from "./UsageExplorer";

View File

@ -0,0 +1,4 @@
export const ENVIRONMENT = {
baseURL: "/api",
// baseURL: "http://104.211.113.204:8080",
};

View File

@ -0,0 +1,3 @@
export enum LOCAL_STORAGE {
METRICS_TIME_IN_DURATION = "metricsTimeDuration",
}

View File

@ -0,0 +1,3 @@
export enum METRICS_PAGE_QUERY_PARAM {
time = "time",
}

View File

@ -1,19 +1,18 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import { Provider } from 'react-redux'; import { Provider } from "react-redux";
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware, compose } from "redux";
import { ThemeSwitcherProvider } from "react-css-theme-switcher"; import { ThemeSwitcherProvider } from "react-css-theme-switcher";
import thunk from 'redux-thunk'; import thunk from "redux-thunk";
// import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom'; // import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import AppWrapper from "./components/AppWrapper";
import "./assets/index.css";
import AppWrapper from './components/AppWrapper'; import { reducers } from "./reducers";
import './assets/index.css';
import { reducers } from './reducers';
// import Signup from './components/Signup'; // import Signup from './components/Signup';
// @ts-ignore
const store = createStore(reducers, applyMiddleware(thunk)) const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
const themes = { const themes = {
dark: `${process.env.PUBLIC_URL}/dark-theme.css`, dark: `${process.env.PUBLIC_URL}/dark-theme.css`,
@ -24,11 +23,10 @@ ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<React.StrictMode> <React.StrictMode>
<ThemeSwitcherProvider themeMap={themes} defaultTheme="dark"> <ThemeSwitcherProvider themeMap={themes} defaultTheme="dark">
<AppWrapper /> <AppWrapper />
{/* <App /> */} {/* <App /> */}
</ThemeSwitcherProvider> </ThemeSwitcherProvider>
</React.StrictMode> </React.StrictMode>
</Provider>, </Provider>,
document.querySelector('#root') document.querySelector("#root"),
); );

View File

@ -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 // Initial global state is time now and 15 minute interval
switch (action.type) { switch (action.type) {
case ActionTypes.updateTimeInterval: case ActionTypes.updateTimeInterval:
return action.payload; return action.payload;
default: default:
return state; return state;
} }
}; };

View File

@ -1,22 +1,37 @@
import { combineReducers } from 'redux'; import { combineReducers } from "redux";
import { traceResponseNew, spansWSameTraceIDResponse, servicesListItem, metricItem, topEndpointListItem, usageDataItem, GlobalTime, customMetricsItem, TraceFilters } from '../actions'; import {
import { updateGlobalTimeReducer } from './global'; traceResponseNew,
import { filteredTraceMetricsReducer, serviceMetricsReducer, serviceTableReducer, topEndpointsReducer } from './metrics'; spansWSameTraceIDResponse,
import { traceFiltersReducer, inputsReducer} from './traceFilters' servicesListItem,
import { traceItemReducer, tracesReducer } from './traces' metricItem,
import { usageDataReducer } from './usage' topEndpointListItem,
usageDataItem,
GlobalTime,
customMetricsItem,
TraceFilters,
} from "../actions";
import { updateGlobalTimeReducer } from "./global";
import {
filteredTraceMetricsReducer,
serviceMetricsReducer,
serviceTableReducer,
topEndpointsReducer,
} from "./metrics";
import { traceFiltersReducer, inputsReducer } from "./traceFilters";
import { traceItemReducer, tracesReducer } from "./traces";
import { usageDataReducer } from "./usage";
export interface StoreState { export interface StoreState {
traceFilters: TraceFilters, traceFilters: TraceFilters;
inputTag: string, inputTag: string;
traces: traceResponseNew, traces: traceResponseNew;
traceItem: spansWSameTraceIDResponse , traceItem: spansWSameTraceIDResponse;
servicesList: servicesListItem[], servicesList: servicesListItem[];
serviceMetrics:metricItem[], serviceMetrics: metricItem[];
topEndpointsList:topEndpointListItem[], topEndpointsList: topEndpointListItem[];
usageDate:usageDataItem[], usageDate: usageDataItem[];
globalTime:GlobalTime, globalTime: GlobalTime;
filteredTraceMetrics:customMetricsItem[], filteredTraceMetrics: customMetricsItem[];
} }
export const reducers = combineReducers<StoreState>({ export const reducers = combineReducers<StoreState>({
@ -31,4 +46,3 @@ export const reducers = combineReducers<StoreState>({
globalTime: updateGlobalTimeReducer, globalTime: updateGlobalTimeReducer,
filteredTraceMetrics: filteredTraceMetricsReducer, filteredTraceMetrics: filteredTraceMetricsReducer,
}); });

View File

@ -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) { switch (action.type) {
case ActionTypes.getServicesList: case ActionTypes.getServicesList:
return action.payload; 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) { switch (action.type) {
case ActionTypes.getServiceMetrics: case ActionTypes.getServiceMetrics:
return action.payload; 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) { switch (action.type) {
case ActionTypes.getTopEndpoints: case ActionTypes.getTopEndpoints:
return action.payload; return action.payload;
@ -27,8 +66,10 @@ export const serviceTableReducer = (state: servicesListItem[] = [{"serviceName":
} }
}; };
export const filteredTraceMetricsReducer = (
export const filteredTraceMetricsReducer = (state: customMetricsItem[] = [{"timestamp": 0, "value": 0}], action: Action) => { state: customMetricsItem[] = [{ timestamp: 0, value: 0 }],
action: Action,
) => {
switch (action.type) { switch (action.type) {
case ActionTypes.getFilteredTraceMetrics: case ActionTypes.getFilteredTraceMetrics:
return action.payload; return action.payload;

View File

@ -1,26 +1,35 @@
import { ActionTypes, TraceFilters, updateInputTagAction, updateTraceFiltersAction } from '../actions'; import {
ActionTypes,
export const traceFiltersReducer = (state:TraceFilters = {'service':'', 'tags':[],'operation':'','latency':{'min':'','max':''}}, action: updateTraceFiltersAction) => { TraceFilters,
updateInputTagAction,
updateTraceFiltersAction,
} from "../actions";
export const traceFiltersReducer = (
state: TraceFilters = {
service: "",
tags: [],
operation: "",
latency: { min: "", max: "" },
},
action: updateTraceFiltersAction,
) => {
switch (action.type) { switch (action.type) {
case ActionTypes.updateTraceFilters: case ActionTypes.updateTraceFilters:
return action.payload; return action.payload;
default: default:
return state; return state;
} }
}; };
export const inputsReducer = (state:string = '', action:updateInputTagAction) => { export const inputsReducer = (
state: string = "",
action: updateInputTagAction,
) => {
switch (action.type) { switch (action.type) {
case ActionTypes.updateInput: case ActionTypes.updateInput:
return action.payload; return action.payload;
default: default:
return state; return state;
}
} }
};

View File

@ -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 // PNOTE - Initializing is a must for state variable otherwise it gives an error in reducer
var spanlistinstance :spanList ={ events: [], segmentID: '', columns: []} ; var spanlistinstance: spanList = { events: [], segmentID: "", columns: [] };
export const tracesReducer = (state: traceResponseNew = {"0": spanlistinstance} , action: Action) => { export const tracesReducer = (
state: traceResponseNew = { "0": spanlistinstance },
action: Action,
) => {
switch (action.type) { switch (action.type) {
case ActionTypes.fetchTraces: case ActionTypes.fetchTraces:
return action.payload; 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) { switch (action.type) {
case ActionTypes.fetchTraceItem: case ActionTypes.fetchTraceItem:
return action.payload; return action.payload;

View File

@ -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) { switch (action.type) {
case ActionTypes.getUsageData: case ActionTypes.getUsageData:
return action.payload; return action.payload;

View File

@ -1,8 +1,8 @@
// dark-theme.less // dark-theme.less
@import '~antd/lib/style/color/colorPalette.less'; @import "~antd/lib/style/color/colorPalette.less";
@import '~antd/dist/antd.less'; @import "~antd/dist/antd.less";
@import '~antd/lib/style/themes/dark.less'; @import "~antd/lib/style/themes/dark.less";
// @primary-color: #00adb5; // @primary-color: #00adb5;
// @border-radius-base: 4px; // @border-radius-base: 4px;

View File

@ -1,8 +1,8 @@
/* light-theme.less */ /* light-theme.less */
@import '~antd/lib/style/color/colorPalette.less'; @import "~antd/lib/style/color/colorPalette.less";
@import '~antd/dist/antd.less'; @import "~antd/dist/antd.less";
@import '~antd/lib/style/themes/default.less'; @import "~antd/lib/style/themes/default.less";
/* These are shared variables that can be extracted to their own file */ /* These are shared variables that can be extracted to their own file */
@primary-color: #00adb5; @primary-color: #00adb5;

View File

@ -1 +1 @@
declare module 'd3-tip'; declare module "d3-tip";

View File

@ -1,2 +1,2 @@
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from "history";
export default createBrowserHistory(); export default createBrowserHistory();

View File

@ -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 // PNOTE - should the data be taken from redux or only through props? - Directly as arguments
export const spanToTreeUtil = (spanlist: span[]): pushDStree => { export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
// Initializing tree. What should be returned is trace is empty? We should have better error handling // 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; // let spans :spanItem[]= trace.spans;
if (spanlist) { if (spanlist) {
// Create a dict with spanIDs as keys // Create a dict with spanIDs as keys
// PNOTE // PNOTE
// Can we now assign different strings as id - Yes // Can we now assign different strings as id - Yes
// https://stackoverflow.com/questions/15877362/declare-and-initialize-a-dictionary-in-typescript // 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++) { for (let i = 0; i < spanlist.length; i++) {
mapped_array[spanlist[i][1]] = spanlist[i]; mapped_array[spanlist[i][1]] = spanlist[i];
@ -31,19 +33,16 @@ export const spanToTreeUtil = (spanlist :span[]) :pushDStree => {
//mapping tags to new structure //mapping tags to new structure
let tags_temp = []; let tags_temp = [];
if (child_span[7]!==null && child_span[8]!==null ) if (child_span[7] !== null && child_span[8] !== null) {
{ if (
if (typeof(child_span[7])==='string' && typeof(child_span[8])==='string') typeof child_span[7] === "string" &&
{ typeof child_span[8] === "string"
tags_temp.push({key:child_span[7],value:child_span[8]}) ) {
tags_temp.push({ key: child_span[7], value: child_span[8] });
} else if (child_span[7].length>0 && child_span[8].length>0) } else if (child_span[7].length > 0 && child_span[8].length > 0) {
{ for (let j = 0; j < child_span[7].length; j++) {
for(let j=0;j<child_span[7].length; j++) tags_temp.push({ key: child_span[7][j], value: child_span[8][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]), time: parseInt(child_span[6]),
startTime: child_span[0], startTime: child_span[0],
tags: tags_temp, tags: tags_temp,
children: mapped_array[id][10] children: mapped_array[id][10],
} };
let referencesArr = mapped_array[id][9] let referencesArr = mapped_array[id][9];
let refArray = [] let refArray = [];
if(typeof(referencesArr) === "string"){ if (typeof referencesArr === "string") {
refArray.push(referencesArr) refArray.push(referencesArr);
} else { } else {
refArray = referencesArr refArray = referencesArr;
} }
let references: RefItem[] = []; let references: RefItem[] = [];
refArray.forEach(element => { refArray.forEach((element) => {
element = element.replaceAll("{", "").replaceAll("}", "").replaceAll(" ", "") element = element
let arr = element.split(",") .replaceAll("{", "")
let refItem = {"traceID": "", "spanID": "", "refType": ""}; .replaceAll("}", "")
arr.forEach(obj => { .replaceAll(" ", "");
let arr2 = obj.split("=") let arr = element.split(",");
let refItem = { traceID: "", spanID: "", refType: "" };
arr.forEach((obj) => {
let arr2 = obj.split("=");
if (arr2[0] === "TraceId") { if (arr2[0] === "TraceId") {
refItem["traceID"] = arr2[1]; refItem["traceID"] = arr2[1];
} else if (arr2[0] === "SpanId") { } else if (arr2[0] === "SpanId") {
@ -87,20 +89,12 @@ export const spanToTreeUtil = (spanlist :span[]) :pushDStree => {
if (references[0].refType === "CHILD_OF") { if (references[0].refType === "CHILD_OF") {
let parentID = references[0].spanID; let parentID = references[0].spanID;
mapped_array[parentID][10].push(push_object); mapped_array[parentID][10].push(push_object);
} }
} } else {
else{
tree = push_object; tree = push_object;
} }
} // end of for loop } // end of for loop
} // end of if(spans) } // end of if(spans)
return tree;
};
return(tree)
}

View File

@ -10,11 +10,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"moduleResolution": "node", "moduleResolution": "node",
@ -22,7 +18,5 @@
"isolatedModules": true, "isolatedModules": true,
"noEmit": true "noEmit": true
}, },
"include": [ "include": ["src"]
"src"
]
} }

View File

@ -2672,7 +2672,7 @@ ansi-cyan@^0.1.1:
dependencies: dependencies:
ansi-wrap "0.1.0" ansi-wrap "0.1.0"
ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
@ -3036,6 +3036,11 @@ astral-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async-done@^1.2.0, async-done@^1.2.2: async-done@^1.2.0, async-done@^1.2.2:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2" resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2"
@ -3958,6 +3963,14 @@ cli-cursor@^3.1.0:
dependencies: dependencies:
restore-cursor "^3.1.0" restore-cursor "^3.1.0"
cli-truncate@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
dependencies:
slice-ansi "^3.0.0"
string-width "^4.2.0"
cli-width@^3.0.0: cli-width@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
@ -4145,6 +4158,11 @@ commander@^4.1.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
commander@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
common-tags@^1.8.0: common-tags@^1.8.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@ -4155,6 +4173,11 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
compare-versions@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
component-emitter@^1.2.1, component-emitter@^1.3.0: component-emitter@^1.2.1, component-emitter@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -5179,6 +5202,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
debug@^4.2.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.2.0: decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@ -5627,7 +5657,7 @@ enhanced-resolve@^4.3.0:
memory-fs "^0.5.0" memory-fs "^0.5.0"
tapable "^1.0.0" tapable "^1.0.0"
enquirer@^2.3.5: enquirer@^2.3.5, enquirer@^2.3.6:
version "2.3.6" version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
@ -6067,7 +6097,7 @@ execa@^1.0.0:
signal-exit "^3.0.0" signal-exit "^3.0.0"
strip-eof "^1.0.0" strip-eof "^1.0.0"
execa@^4.0.0: execa@^4.0.0, execa@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
@ -6302,7 +6332,7 @@ figgy-pudding@^3.5.1:
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
figures@^3.0.0: figures@^3.0.0, figures@^3.2.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
@ -6412,6 +6442,21 @@ find-up@^3.0.0:
dependencies: dependencies:
locate-path "^3.0.0" locate-path "^3.0.0"
find-up@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
find-versions@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965"
integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==
dependencies:
semver-regex "^3.1.2"
findup-sync@^2.0.0: findup-sync@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc"
@ -7250,6 +7295,22 @@ human-signals@^1.1.1:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
husky@4.3.8:
version "4.3.8"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d"
integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==
dependencies:
chalk "^4.0.0"
ci-info "^2.0.0"
compare-versions "^3.6.0"
cosmiconfig "^7.0.0"
find-versions "^4.0.0"
opencollective-postinstall "^2.0.2"
pkg-dir "^5.0.0"
please-upgrade-node "^3.2.0"
slash "^3.0.0"
which-pm-runs "^1.0.0"
hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3: hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
@ -8771,6 +8832,41 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
lint-staged@10.5.3:
version "10.5.3"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.3.tgz#c682838b3eadd4c864d1022da05daa0912fb1da5"
integrity sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg==
dependencies:
chalk "^4.1.0"
cli-truncate "^2.1.0"
commander "^6.2.0"
cosmiconfig "^7.0.0"
debug "^4.2.0"
dedent "^0.7.0"
enquirer "^2.3.6"
execa "^4.1.0"
listr2 "^3.2.2"
log-symbols "^4.0.0"
micromatch "^4.0.2"
normalize-path "^3.0.0"
please-upgrade-node "^3.2.0"
string-argv "0.3.1"
stringify-object "^3.3.0"
listr2@^3.2.2:
version "3.2.3"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.2.3.tgz#ef9e0d790862f038dde8a9837be552b1adfd1c07"
integrity sha512-vUb80S2dSUi8YxXahO8/I/s29GqnOL8ozgHVLjfWQXa03BNEeS1TpBLjh2ruaqq5ufx46BRGvfymdBSuoXET5w==
dependencies:
chalk "^4.1.0"
cli-truncate "^2.1.0"
figures "^3.2.0"
indent-string "^4.0.0"
log-update "^4.0.0"
p-map "^4.0.0"
rxjs "^6.6.3"
through "^2.3.8"
load-json-file@^1.0.0: load-json-file@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@ -8847,6 +8943,13 @@ locate-path@^5.0.0:
dependencies: dependencies:
p-locate "^4.1.0" p-locate "^4.1.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
lodash._reinterpolate@^3.0.0: lodash._reinterpolate@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@ -8927,6 +9030,23 @@ lodash.uniq@^4.3.0, lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
log-symbols@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
dependencies:
chalk "^4.0.0"
log-update@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
dependencies:
ansi-escapes "^4.3.0"
cli-cursor "^3.1.0"
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
loglevel@^1.6.8: loglevel@^1.6.8:
version "1.7.0" version "1.7.0"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
@ -9756,6 +9876,11 @@ open@^7.0.2:
is-docker "^2.0.0" is-docker "^2.0.0"
is-wsl "^2.1.1" is-wsl "^2.1.1"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
opn@^5.5.0: opn@^5.5.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
@ -9883,6 +10008,13 @@ p-locate@^4.1.0:
dependencies: dependencies:
p-limit "^2.2.0" p-limit "^2.2.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
p-map@^2.0.0: p-map@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@ -10192,6 +10324,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies: dependencies:
find-up "^4.0.0" find-up "^4.0.0"
pkg-dir@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760"
integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==
dependencies:
find-up "^5.0.0"
pkg-up@3.1.0: pkg-up@3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
@ -10199,6 +10338,13 @@ pkg-up@3.1.0:
dependencies: dependencies:
find-up "^3.0.0" find-up "^3.0.0"
please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
dependencies:
semver-compare "^1.0.0"
plugin-error@^0.1.2: plugin-error@^0.1.2:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
@ -10940,6 +11086,11 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prettier@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
pretty-bytes@^5.3.0: pretty-bytes@^5.3.0:
version "5.4.1" version "5.4.1"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b"
@ -12496,7 +12647,7 @@ rw@1:
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
rxjs@^6.6.0: rxjs@^6.6.0, rxjs@^6.6.3:
version "6.6.3" version "6.6.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
@ -12627,6 +12778,11 @@ selfsigned@^1.10.7:
dependencies: dependencies:
node-forge "^0.10.0" node-forge "^0.10.0"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
semver-greatest-satisfied-range@^1.1.0: semver-greatest-satisfied-range@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
@ -12634,6 +12790,11 @@ semver-greatest-satisfied-range@^1.1.0:
dependencies: dependencies:
sver-compat "^1.5.0" sver-compat "^1.5.0"
semver-regex@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807"
integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@ -12838,6 +12999,24 @@ slice-ansi@^2.1.0:
astral-regex "^1.0.0" astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0" is-fullwidth-code-point "^2.0.0"
slice-ansi@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
slice-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
snapdragon-node@^2.0.1: snapdragon-node@^2.0.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@ -13130,6 +13309,11 @@ strict-uri-encode@^1.0.0:
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
string-argv@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-convert@^0.2.0: string-convert@^0.2.0:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
@ -13530,7 +13714,7 @@ through2@^2.0.0, through2@^2.0.3, through2@~2.0.0:
readable-stream "~2.3.6" readable-stream "~2.3.6"
xtend "~4.0.1" xtend "~4.0.1"
through@^2.3.6: through@^2.3.6, through@^2.3.8:
version "2.3.8" version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@ -14444,6 +14628,11 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which-pm-runs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
which@^1.2.14, which@^1.2.9, which@^1.3.1: which@^1.2.14, which@^1.2.9, which@^1.3.1:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"