mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 04:39:59 +08:00
Fix(FE): some eslint error are fixed (#382)
* chore(UI): @types/node version is updated and port sync finder is removed * chore(UI): tsconfig is updated * fix(bug) webpack config is updated * fix(bug): some eslint error is now fixed * chore(lock): yarn lock is fixed
This commit is contained in:
parent
73b5134971
commit
4dfbdd2d63
@ -19,6 +19,8 @@
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on, config: Cypress.ConfigOptions): void => {};
|
||||
module.exports = (): void => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export {};
|
||||
|
@ -6,7 +6,7 @@
|
||||
"noEmit": true,
|
||||
// be explicit about types included
|
||||
// to avoid clashing with Jest types
|
||||
"types": ["cypress", "@testing-library/cypress"],
|
||||
"types": ["cypress", "@testing-library/cypress", "node"],
|
||||
"isolatedModules": false
|
||||
},
|
||||
"include": ["../node_modules/cypress", "./**/*.ts"]
|
||||
|
@ -18,13 +18,6 @@ export const ServiceMapPage = Loadable(
|
||||
),
|
||||
);
|
||||
|
||||
export const TraceDetailPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "TraceDetailPage" */ 'modules/Traces/TraceDetail'
|
||||
),
|
||||
);
|
||||
|
||||
export const TraceDetailPages = Loadable(
|
||||
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/TraceDetails'),
|
||||
);
|
||||
|
@ -17,7 +17,6 @@ import {
|
||||
ServicesTablePage,
|
||||
SettingsPage,
|
||||
SignupPage,
|
||||
TraceDetailPage,
|
||||
TraceDetailPages,
|
||||
TraceGraphPage,
|
||||
UsageExplorerPage,
|
||||
@ -64,11 +63,6 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
component: InstrumentationPage,
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES,
|
||||
exact: true,
|
||||
component: TraceDetailPage,
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_DASHBOARD,
|
||||
exact: true,
|
||||
|
@ -12,5 +12,5 @@ export const SpinerStyle = styled.div<Props>`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: ${({ height = '100vh' }) => height};
|
||||
height: ${({ height = '100vh' }): number | string => height};
|
||||
`;
|
||||
|
@ -2,7 +2,6 @@ const ROUTES = {
|
||||
SIGN_UP: '/signup',
|
||||
SERVICE_METRICS: '/application/:servicename',
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACES: '/traces',
|
||||
TRACE: '/trace',
|
||||
TRACE_GRAPH: '/traces/:id',
|
||||
SETTINGS: '/settings',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Typography } from 'antd';
|
||||
import { ChartData, ChartOptions } from 'chart.js';
|
||||
import { ChartData } from 'chart.js';
|
||||
import Graph, { graphOnClickHandler } from 'components/Graph';
|
||||
import ValueGraph from 'components/ValueGraph';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Button, Select as DefaultSelect } from 'antd';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getDefaultOption, getOptions, Time } from './config';
|
||||
import { Container, Form, FormItem } from './styles';
|
||||
@ -75,7 +75,7 @@ const DateTimeSelection = ({
|
||||
false,
|
||||
);
|
||||
|
||||
const { maxTime, minTime, selectedTime, loading } = useSelector<
|
||||
const { maxTime, minTime, selectedTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
@ -56,7 +56,15 @@ const DescriptionOfDashboard = ({
|
||||
} else {
|
||||
toggleEditMode();
|
||||
}
|
||||
}, [isEditMode, updatedTitle, updatedTags, updatedDescription]);
|
||||
}, [
|
||||
isEditMode,
|
||||
updatedTitle,
|
||||
updatedTags,
|
||||
updatedDescription,
|
||||
selectedDashboard,
|
||||
toggleEditMode,
|
||||
updateDashboardTitleDescriptionTags,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -6,17 +6,16 @@ import { AppState } from 'store/reducers';
|
||||
const { Option } = Select;
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
|
||||
import {
|
||||
GetTraceVisualAggregates,
|
||||
GetTraceVisualAggregatesProps,
|
||||
} from 'store/actions/trace/getTraceVisualAgrregates';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { aggregation_options, entity } from './config';
|
||||
import { Card, CustomVisualizationsTitle, FormItem, Space } from './styles';
|
||||
import TraceCustomGraph from './TraceCustomGraph';
|
||||
import {
|
||||
GetTraceVisualAggregates,
|
||||
GetTraceVisualAggregatesProps,
|
||||
} from 'store/actions/trace/getTraceVisualAgrregates';
|
||||
|
||||
const TraceCustomVisualisation = ({
|
||||
getTraceVisualAggregates,
|
||||
|
@ -5,12 +5,7 @@ import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { TagItem } from 'store/actions';
|
||||
import {
|
||||
UpdateSelectedLatency,
|
||||
UpdateSelectedOperation,
|
||||
UpdateSelectedService,
|
||||
UpdateSelectedTags,
|
||||
} from 'store/actions/trace';
|
||||
import { UpdateSelectedTags } from 'store/actions/trace';
|
||||
import {
|
||||
UpdateSelectedData,
|
||||
UpdateSelectedDataProps,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Input, Typography, notification } from 'antd';
|
||||
import { Button, Input, notification, Typography } from 'antd';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
@ -237,7 +237,13 @@ const TraceList = ({
|
||||
} ms`,
|
||||
});
|
||||
}
|
||||
}, [selectedService, selectedOperation, selectedKind, selectedLatency]);
|
||||
}, [
|
||||
selectedService,
|
||||
selectedOperation,
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
form_basefilter,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,26 +1,26 @@
|
||||
import React, { useState } from "react";
|
||||
import { servicesItem } from "store/actions";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Select } from "antd";
|
||||
import styled from "styled-components";
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Select } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { servicesItem } from 'store/actions';
|
||||
import styled from 'styled-components';
|
||||
const { Option } = Select;
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const Container = styled.div`
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
.info {
|
||||
display:flex;
|
||||
font-family: Roboto;
|
||||
margin-left: auto;
|
||||
margin-right: 12px;
|
||||
color: #4F4F4F;
|
||||
font-size: 14px;
|
||||
.anticon-info-circle {
|
||||
margin-top: 22px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
.info {
|
||||
display: flex;
|
||||
font-family: Roboto;
|
||||
margin-left: auto;
|
||||
margin-right: 12px;
|
||||
color: #4f4f4f;
|
||||
font-size: 14px;
|
||||
.anticon-info-circle {
|
||||
margin-top: 22px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface SelectServiceProps {
|
||||
@ -30,17 +30,20 @@ interface SelectServiceProps {
|
||||
}
|
||||
|
||||
const defaultOption = {
|
||||
serviceName: "Default"
|
||||
serviceName: 'Default',
|
||||
};
|
||||
|
||||
const SelectService = (props: SelectServiceProps) => {
|
||||
const [selectedVal, setSelectedVal] = useState<string>(defaultOption.serviceName);
|
||||
const SelectService = (props: SelectServiceProps): JSX.Element => {
|
||||
const [selectedVal, setSelectedVal] = useState<string>(
|
||||
defaultOption.serviceName,
|
||||
);
|
||||
const { zoomToService, zoomToDefault } = props;
|
||||
const services = cloneDeep(props.services);
|
||||
services.unshift(defaultOption)
|
||||
const handleSelect = (value: string) => {
|
||||
if(value === defaultOption.serviceName){
|
||||
zoomToDefault()
|
||||
services.unshift(defaultOption);
|
||||
|
||||
const handleSelect = (value: string): void => {
|
||||
if (value === defaultOption.serviceName) {
|
||||
zoomToDefault();
|
||||
} else {
|
||||
zoomToService(value);
|
||||
}
|
||||
@ -49,7 +52,7 @@ const SelectService = (props: SelectServiceProps) => {
|
||||
return (
|
||||
<Container>
|
||||
<Select
|
||||
style={{ width: 270, marginBottom: "56px" }}
|
||||
style={{ width: 270, marginBottom: '56px' }}
|
||||
placeholder="Select a service"
|
||||
onChange={handleSelect}
|
||||
value={selectedVal}
|
||||
@ -60,14 +63,16 @@ const SelectService = (props: SelectServiceProps) => {
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<div className='info'>
|
||||
<InfoCircleOutlined />
|
||||
<div>
|
||||
|
||||
<div>-> Size of circles is proportial to the number of requests served by each node </div>
|
||||
<div>-> Click on node name to reposition the node</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info">
|
||||
<InfoCircleOutlined />
|
||||
<div>
|
||||
<div>
|
||||
> Size of circles is proportial to the number of requests served by
|
||||
each node
|
||||
</div>
|
||||
<div>> Click on node name to reposition the node</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
@ -34,8 +34,8 @@ const Container = styled.div`
|
||||
interface ServiceMapProps extends RouteComponentProps<any> {
|
||||
serviceMap: serviceMapStore;
|
||||
globalTime: GlobalTime;
|
||||
getServiceMapItems: Function;
|
||||
getDetailedServiceMapItems: Function;
|
||||
getServiceMapItems: (time: GlobalTime) => void;
|
||||
getDetailedServiceMapItems: (time: GlobalTime) => void;
|
||||
}
|
||||
interface graphNode {
|
||||
id: string;
|
||||
@ -51,7 +51,7 @@ export interface graphDataType {
|
||||
links: graphLink[];
|
||||
}
|
||||
|
||||
const ServiceMap = (props: ServiceMapProps) => {
|
||||
const ServiceMap = (props: ServiceMapProps): JSX.Element => {
|
||||
const fgRef = useRef();
|
||||
|
||||
const {
|
||||
@ -68,7 +68,7 @@ const ServiceMap = (props: ServiceMapProps) => {
|
||||
*/
|
||||
getServiceMapItems(globalTime);
|
||||
getDetailedServiceMapItems(globalTime);
|
||||
}, [globalTime]);
|
||||
}, [globalTime, getServiceMapItems, getDetailedServiceMapItems]);
|
||||
|
||||
useEffect(() => {
|
||||
fgRef.current && fgRef.current.d3Force('charge').strength(-400);
|
||||
@ -77,12 +77,14 @@ const ServiceMap = (props: ServiceMapProps) => {
|
||||
return <Spinner size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
const zoomToService = (value: string) => {
|
||||
fgRef && fgRef.current.zoomToFit(700, getZoomPx(), (e) => e.id === value);
|
||||
const zoomToService = (value: string): void => {
|
||||
fgRef &&
|
||||
fgRef.current &&
|
||||
fgRef.current.zoomToFit(700, getZoomPx(), (e) => e.id === value);
|
||||
};
|
||||
|
||||
const zoomToDefault = () => {
|
||||
fgRef && fgRef.current.zoomToFit(100, 120);
|
||||
fgRef && fgRef.current && fgRef.current.zoomToFit(100, 120);
|
||||
};
|
||||
|
||||
const { nodes, links } = getGraphData(serviceMap);
|
||||
|
@ -1,94 +0,0 @@
|
||||
import { Col, Form, InputNumber, Modal, Row } from 'antd';
|
||||
import { NamePath, Store } from 'antd/lib/form/interface';
|
||||
import React from 'react';
|
||||
|
||||
interface LatencyModalFormProps {
|
||||
onCreate: (values: Store) => void; //Store is defined in antd forms library
|
||||
onCancel: () => void;
|
||||
latencyFilterValues: { min: string; max: string };
|
||||
}
|
||||
|
||||
const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
||||
onCreate,
|
||||
onCancel,
|
||||
latencyFilterValues,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const validateMinValue = ({
|
||||
getFieldValue,
|
||||
}: {
|
||||
getFieldValue: (name: NamePath) => any;
|
||||
}) => ({
|
||||
validator(_, value: any) {
|
||||
if (value < getFieldValue('max')) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('Min value should be less than Max value'));
|
||||
},
|
||||
});
|
||||
|
||||
const validateMaxValue = ({
|
||||
getFieldValue,
|
||||
}: {
|
||||
getFieldValue: (name: NamePath) => any;
|
||||
}) => ({
|
||||
validator(_, value: any) {
|
||||
if (value > getFieldValue('min')) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error('Max value should be greater than Min value'),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={true}
|
||||
title="Chose min and max values of Latency"
|
||||
okText="Apply"
|
||||
cancelText="Cancel"
|
||||
onCancel={onCancel}
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
form.resetFields();
|
||||
onCreate(values); // giving error for values
|
||||
})
|
||||
.catch((info) => {
|
||||
// console.log('Validate Failed:', info);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={latencyFilterValues}
|
||||
>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="min"
|
||||
label="Min (in ms)"
|
||||
rules={[validateMinValue]}
|
||||
// rules={[{ required: true, message: 'Please input the title of collection!' }]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="max" label="Max (in ms)" rules={[validateMaxValue]}>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* </Input.Group> */}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default LatencyModalForm;
|
@ -54,7 +54,7 @@ const CardContainer = styled(Card)`
|
||||
}
|
||||
`;
|
||||
|
||||
const SelectedSpanDetails = (props: SelectedSpanDetailsProps) => {
|
||||
const SelectedSpanDetails = (props: SelectedSpanDetailsProps): JSX.Element => {
|
||||
const spanTags = props.data?.tags;
|
||||
const service = props.data?.name?.split(':')[0];
|
||||
const operation = props.data?.name?.split(':')[1];
|
||||
|
@ -1,272 +0,0 @@
|
||||
import { Form, Select, Space } from 'antd';
|
||||
import Graph from 'components/Graph';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { TraceFilters } from 'store/actions';
|
||||
import { getFilteredTraceMetrics } from 'store/actions/MetricsActions';
|
||||
import { customMetricsItem } from 'store/actions/MetricsActions';
|
||||
import { AppState } from 'store/reducers';
|
||||
const { Option } = Select;
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CustomGraphContainer,
|
||||
CustomVisualizationsTitle,
|
||||
} from './styles';
|
||||
|
||||
const entity = [
|
||||
{
|
||||
title: 'Calls',
|
||||
key: 'calls',
|
||||
dataindex: 'calls',
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
key: 'duration',
|
||||
dataindex: 'duration',
|
||||
},
|
||||
{
|
||||
title: 'Error',
|
||||
key: 'error',
|
||||
dataindex: 'error',
|
||||
},
|
||||
{
|
||||
title: 'Status Code',
|
||||
key: 'status_code',
|
||||
dataindex: 'status_code',
|
||||
},
|
||||
];
|
||||
|
||||
const aggregation_options = [
|
||||
{
|
||||
linked_entity: 'calls',
|
||||
default_selected: { title: 'Count', dataindex: 'count' },
|
||||
options_available: [
|
||||
{ title: 'Count', dataindex: 'count' },
|
||||
{ title: 'Rate (per sec)', dataindex: 'rate_per_sec' },
|
||||
],
|
||||
},
|
||||
{
|
||||
linked_entity: 'duration',
|
||||
default_selected: { title: 'p99', dataindex: 'p99' },
|
||||
// options_available: [ {title:'Avg', dataindex:'avg'}, {title:'Max', dataindex:'max'},{title:'Min', dataindex:'min'}, {title:'p50', dataindex:'p50'},{title:'p95', dataindex:'p95'}, {title:'p95', dataindex:'p95'}]
|
||||
options_available: [
|
||||
{ title: 'p50', dataindex: 'p50' },
|
||||
{ title: 'p95', dataindex: 'p95' },
|
||||
{ title: 'p99', dataindex: 'p99' },
|
||||
],
|
||||
},
|
||||
{
|
||||
linked_entity: 'error',
|
||||
default_selected: { title: 'Count', dataindex: 'count' },
|
||||
options_available: [
|
||||
{ title: 'Count', dataindex: 'count' },
|
||||
{ title: 'Rate (per sec)', dataindex: 'rate_per_sec' },
|
||||
],
|
||||
},
|
||||
{
|
||||
linked_entity: 'status_code',
|
||||
default_selected: { title: 'Count', dataindex: 'count' },
|
||||
options_available: [{ title: 'Count', dataindex: 'count' }],
|
||||
},
|
||||
];
|
||||
|
||||
interface TraceCustomVisualizationsProps {
|
||||
filteredTraceMetrics: customMetricsItem[];
|
||||
globalTime: GlobalTime;
|
||||
getFilteredTraceMetrics: (value: string, time: GlobalTime) => void;
|
||||
traceFilters: TraceFilters;
|
||||
}
|
||||
|
||||
const _TraceCustomVisualizations = (
|
||||
props: TraceCustomVisualizationsProps,
|
||||
): JSX.Element => {
|
||||
const [selectedEntity, setSelectedEntity] = useState('calls');
|
||||
const [selectedAggOption, setSelectedAggOption] = useState('count');
|
||||
const [form] = Form.useForm();
|
||||
const selectedStep = '60';
|
||||
const {
|
||||
filteredTraceMetrics,
|
||||
getFilteredTraceMetrics,
|
||||
globalTime,
|
||||
traceFilters,
|
||||
} = props;
|
||||
const { loading } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
// Step should be multiples of 60, 60 -> 1 min
|
||||
useEffect(() => {
|
||||
let request_string =
|
||||
'service=' +
|
||||
traceFilters.service +
|
||||
'&operation=' +
|
||||
traceFilters.operation +
|
||||
'&maxDuration=' +
|
||||
traceFilters.latency?.max +
|
||||
'&minDuration=' +
|
||||
traceFilters.latency?.min +
|
||||
'&kind=' +
|
||||
traceFilters.kind;
|
||||
if (traceFilters.tags)
|
||||
request_string =
|
||||
request_string +
|
||||
'&tags=' +
|
||||
encodeURIComponent(JSON.stringify(traceFilters.tags));
|
||||
if (selectedEntity)
|
||||
request_string =
|
||||
request_string + '&dimension=' + selectedEntity.toLowerCase();
|
||||
if (selectedAggOption)
|
||||
request_string =
|
||||
request_string + '&aggregation_option=' + selectedAggOption.toLowerCase();
|
||||
if (selectedStep) request_string = request_string + '&step=' + selectedStep;
|
||||
const plusMinus15 = {
|
||||
minTime: globalTime.minTime - 15 * 60 * 1000000000,
|
||||
maxTime: globalTime.maxTime + 15 * 60 * 1000000000,
|
||||
};
|
||||
|
||||
/*
|
||||
Call the apis only when the route is loaded.
|
||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||
*/
|
||||
if (loading === false) {
|
||||
getFilteredTraceMetrics(request_string, plusMinus15);
|
||||
}
|
||||
}, [
|
||||
selectedEntity,
|
||||
selectedAggOption,
|
||||
traceFilters,
|
||||
getFilteredTraceMetrics,
|
||||
globalTime,
|
||||
loading,
|
||||
]);
|
||||
|
||||
//Custom metrics API called if time, tracefilters, selected entity or agg option changes
|
||||
|
||||
// PNOTE - Can also use 'coordinate' option in antd Select for implementing this - https://ant.design/components/select/
|
||||
const handleFormValuesChange = (changedValues: any): void => {
|
||||
const formFieldName = Object.keys(changedValues)[0];
|
||||
if (formFieldName === 'entity') {
|
||||
const temp_entity = aggregation_options.filter(
|
||||
(item) => item.linked_entity === changedValues[formFieldName],
|
||||
)[0];
|
||||
|
||||
form.setFieldsValue({
|
||||
agg_options: temp_entity.default_selected.title,
|
||||
// PNOTE - TO DO Check if this has the same behaviour as selecting an option?
|
||||
});
|
||||
|
||||
const temp = form.getFieldsValue(['agg_options', 'entity']);
|
||||
|
||||
setSelectedEntity(temp.entity);
|
||||
setSelectedAggOption(temp.agg_options);
|
||||
//form.setFieldsValue({ agg_options: aggregation_options.filter( item => item.linked_entity === selectedEntity )[0] }); //reset product selection
|
||||
// PNOTE - https://stackoverflow.com/questions/64377293/update-select-option-list-based-on-other-select-field-selection-ant-design
|
||||
}
|
||||
|
||||
if (formFieldName === 'agg_options') {
|
||||
setSelectedAggOption(changedValues[formFieldName]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CustomVisualizationsTitle>Custom Visualizations</CustomVisualizationsTitle>
|
||||
<Form
|
||||
form={form}
|
||||
onValuesChange={handleFormValuesChange}
|
||||
initialValues={{
|
||||
agg_options: 'Count',
|
||||
chart_style: 'line',
|
||||
interval: '5m',
|
||||
group_by: 'none',
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<Form.Item name="entity">
|
||||
<Select defaultValue={selectedEntity} style={{ width: 120 }} allowClear>
|
||||
{entity.map((item) => (
|
||||
<Option key={item.key} value={item.dataindex}>
|
||||
{item.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="agg_options">
|
||||
<Select style={{ width: 120 }} allowClear>
|
||||
{aggregation_options
|
||||
.filter((item) => item.linked_entity === selectedEntity)[0]
|
||||
.options_available.map((item) => (
|
||||
<Option key={item.dataindex} value={item.dataindex}>
|
||||
{item.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="chart_style">
|
||||
<Select style={{ width: 120 }} allowClear>
|
||||
<Option value="line">Line Chart</Option>
|
||||
<Option value="bar">Bar Chart</Option>
|
||||
<Option value="area">Area Chart</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="interval">
|
||||
<Select style={{ width: 120 }} allowClear>
|
||||
<Option value="1m">1 min</Option>
|
||||
<Option value="5m">5 min</Option>
|
||||
<Option value="30m">30 min</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{/* Need heading for each option */}
|
||||
<Form.Item name="group_by">
|
||||
<Select style={{ width: 120 }} allowClear>
|
||||
<Option value="none">Group By</Option>
|
||||
<Option value="status">Status Code</Option>
|
||||
<Option value="protocol">Protocol</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Form>
|
||||
|
||||
<CustomGraphContainer>
|
||||
<Graph
|
||||
type="line"
|
||||
data={{
|
||||
labels: filteredTraceMetrics.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
data: filteredTraceMetrics.map((e) => e.value),
|
||||
borderColor: colors[0],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</CustomGraphContainer>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: AppState,
|
||||
): {
|
||||
filteredTraceMetrics: customMetricsItem[];
|
||||
globalTime: GlobalTime;
|
||||
traceFilters: TraceFilters;
|
||||
} => {
|
||||
return {
|
||||
filteredTraceMetrics: state.metricsData.customMetricsItem,
|
||||
globalTime: state.globalTime,
|
||||
traceFilters: state.traceFilters,
|
||||
};
|
||||
};
|
||||
|
||||
export const TraceCustomVisualizations = connect(mapStateToProps, {
|
||||
getFilteredTraceMetrics: getFilteredTraceMetrics,
|
||||
})(_TraceCustomVisualizations);
|
@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { TraceCustomVisualizations } from './TraceCustomVisualizations';
|
||||
import { TraceFilter } from './TraceFilter';
|
||||
import { TraceList } from './TraceList';
|
||||
|
||||
const TraceDetail = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<TraceFilter />
|
||||
<TraceCustomVisualizations />
|
||||
<TraceList />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TraceDetail;
|
@ -1,487 +0,0 @@
|
||||
import { AutoComplete, Button, Form, Input, Select, Typography } from 'antd';
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import api from 'api';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { fetchTraces, TraceFilters, updateTraceFilters } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FilterStateDisplay } from './FilterStateDisplay';
|
||||
import LatencyModalForm from './LatencyModalForm';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const InfoWrapper = styled.div`
|
||||
padding-top: 10px;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
interface TraceFilterProps {
|
||||
traceFilters: TraceFilters;
|
||||
globalTime: GlobalTime;
|
||||
updateTraceFilters: (props: TraceFilters) => void;
|
||||
fetchTraces: (globalTime: GlobalTime, filter_params: string) => void;
|
||||
}
|
||||
|
||||
interface TagKeyOptionItem {
|
||||
tagKeys: string;
|
||||
tagCount: number;
|
||||
}
|
||||
|
||||
interface ISpanKind {
|
||||
label: 'SERVER' | 'CLIENT';
|
||||
value: string;
|
||||
}
|
||||
|
||||
const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
||||
const [serviceList, setServiceList] = useState<string[]>([]);
|
||||
const [operationList, setOperationsList] = useState<string[]>([]);
|
||||
const [tagKeyOptions, setTagKeyOptions] = useState<TagKeyOptionItem[]>([]);
|
||||
const location = useLocation();
|
||||
const urlParams = useMemo(() => {
|
||||
return new URLSearchParams(location.search.split('?')[1]);
|
||||
}, [location.search]);
|
||||
|
||||
const { loading } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const { updateTraceFilters, traceFilters, globalTime, fetchTraces } = props;
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
const [latencyFilterValues, setLatencyFilterValues] = useState<{
|
||||
min: string;
|
||||
max: string;
|
||||
}>({
|
||||
min: '100',
|
||||
max: '500',
|
||||
});
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [form_basefilter] = Form.useForm();
|
||||
|
||||
const handleChangeOperation = useCallback(
|
||||
(value: string) => {
|
||||
updateTraceFilters({ ...traceFilters, operation: value });
|
||||
},
|
||||
[traceFilters, updateTraceFilters],
|
||||
);
|
||||
|
||||
const populateData = useCallback(
|
||||
(value: string) => {
|
||||
if (loading === false) {
|
||||
const service_request = '/service/' + value + '/operations';
|
||||
api.get<string[]>(service_request).then((response) => {
|
||||
// form_basefilter.resetFields(['operation',])
|
||||
setOperationsList(response.data);
|
||||
});
|
||||
|
||||
const tagkeyoptions_request = '/tags?service=' + value;
|
||||
api.get<TagKeyOptionItem[]>(tagkeyoptions_request).then((response) => {
|
||||
setTagKeyOptions(response.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
[loading],
|
||||
);
|
||||
|
||||
const handleChangeService = useCallback(
|
||||
(value: string) => {
|
||||
populateData(value);
|
||||
updateTraceFilters({ ...traceFilters, service: value });
|
||||
},
|
||||
[traceFilters, updateTraceFilters, populateData],
|
||||
);
|
||||
|
||||
const spanKindList: ISpanKind[] = [
|
||||
{
|
||||
label: 'SERVER',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'CLIENT',
|
||||
value: '3',
|
||||
},
|
||||
];
|
||||
|
||||
const handleApplyFilterForm = useCallback(
|
||||
(values: any): void => {
|
||||
updateTraceFilters({
|
||||
service: values.service,
|
||||
operation: values.operation,
|
||||
latency: {
|
||||
max: '',
|
||||
min: '',
|
||||
},
|
||||
kind: values.kind,
|
||||
});
|
||||
},
|
||||
[updateTraceFilters],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
handleApplyFilterForm({
|
||||
service: '',
|
||||
tags: [],
|
||||
operation: '',
|
||||
latency: { min: '', max: '' },
|
||||
kind: '',
|
||||
});
|
||||
}, [handleApplyFilterForm]);
|
||||
|
||||
const onTagFormSubmit = useCallback(
|
||||
(values) => {
|
||||
if (traceFilters.tags) {
|
||||
// If there are existing tag filters present
|
||||
updateTraceFilters({
|
||||
service: traceFilters.service,
|
||||
operation: traceFilters.operation,
|
||||
latency: traceFilters.latency,
|
||||
tags: [
|
||||
...traceFilters.tags,
|
||||
{
|
||||
key: values.tag_key,
|
||||
value: values.tag_value,
|
||||
operator: values.operator,
|
||||
},
|
||||
],
|
||||
kind: traceFilters.kind,
|
||||
});
|
||||
} else {
|
||||
updateTraceFilters({
|
||||
service: traceFilters.service,
|
||||
operation: traceFilters.operation,
|
||||
latency: traceFilters.latency,
|
||||
tags: [
|
||||
{
|
||||
key: values.tag_key,
|
||||
value: values.tag_value,
|
||||
operator: values.operator,
|
||||
},
|
||||
],
|
||||
kind: traceFilters.kind,
|
||||
});
|
||||
}
|
||||
|
||||
form.resetFields();
|
||||
},
|
||||
[form, traceFilters, updateTraceFilters],
|
||||
);
|
||||
|
||||
const counter = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading === false && counter.current === 0) {
|
||||
counter.current = 1;
|
||||
api
|
||||
.get<string[]>(`/services/list`)
|
||||
.then((response) => {
|
||||
setServiceList(response.data);
|
||||
})
|
||||
.then(() => {
|
||||
const operationName = urlParams.get(METRICS_PAGE_QUERY_PARAM.operation);
|
||||
const serviceName = urlParams.get(METRICS_PAGE_QUERY_PARAM.service);
|
||||
const errorTag = urlParams.get(METRICS_PAGE_QUERY_PARAM.error);
|
||||
if (operationName && serviceName) {
|
||||
updateTraceFilters({
|
||||
...traceFilters,
|
||||
operation: operationName,
|
||||
service: serviceName,
|
||||
kind: '',
|
||||
});
|
||||
populateData(serviceName);
|
||||
} else if (serviceName && errorTag) {
|
||||
updateTraceFilters({
|
||||
...traceFilters,
|
||||
service: serviceName,
|
||||
tags: [
|
||||
{
|
||||
key: METRICS_PAGE_QUERY_PARAM.error,
|
||||
value: errorTag,
|
||||
operator: 'equals',
|
||||
},
|
||||
],
|
||||
kind: '',
|
||||
});
|
||||
} else {
|
||||
if (operationName) {
|
||||
handleChangeOperation(operationName);
|
||||
}
|
||||
if (serviceName) {
|
||||
handleChangeService(serviceName);
|
||||
}
|
||||
if (errorTag) {
|
||||
onTagFormSubmit({
|
||||
tag_key: METRICS_PAGE_QUERY_PARAM.error,
|
||||
tag_value: errorTag,
|
||||
operator: 'equals',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [
|
||||
handleChangeOperation,
|
||||
onTagFormSubmit,
|
||||
handleChangeService,
|
||||
traceFilters,
|
||||
urlParams,
|
||||
updateTraceFilters,
|
||||
populateData,
|
||||
loading,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
let request_string =
|
||||
'service=' +
|
||||
traceFilters.service +
|
||||
'&operation=' +
|
||||
traceFilters.operation +
|
||||
'&maxDuration=' +
|
||||
traceFilters.latency?.max +
|
||||
'&minDuration=' +
|
||||
traceFilters.latency?.min +
|
||||
'&kind=' +
|
||||
traceFilters.kind;
|
||||
if (traceFilters.tags)
|
||||
request_string =
|
||||
request_string +
|
||||
'&tags=' +
|
||||
encodeURIComponent(JSON.stringify(traceFilters.tags));
|
||||
|
||||
if (loading === false) {
|
||||
fetchTraces(globalTime, request_string);
|
||||
}
|
||||
}, [traceFilters, fetchTraces, loading, globalTime]);
|
||||
|
||||
useEffect(() => {
|
||||
let latencyButtonText = 'Latency';
|
||||
if (traceFilters.latency?.min === '' && traceFilters.latency?.max !== '')
|
||||
latencyButtonText =
|
||||
'Latency<' +
|
||||
(parseInt(traceFilters.latency?.max) / 1000000).toString() +
|
||||
'ms';
|
||||
else if (traceFilters.latency?.min !== '' && traceFilters.latency?.max === '')
|
||||
latencyButtonText =
|
||||
'Latency>' +
|
||||
(parseInt(traceFilters.latency?.min) / 1000000).toString() +
|
||||
'ms';
|
||||
else if (
|
||||
traceFilters.latency !== undefined &&
|
||||
traceFilters.latency?.min !== '' &&
|
||||
traceFilters.latency?.max !== ''
|
||||
)
|
||||
latencyButtonText =
|
||||
(parseInt(traceFilters.latency.min) / 1000000).toString() +
|
||||
'ms <Latency<' +
|
||||
(parseInt(traceFilters.latency.max) / 1000000).toString() +
|
||||
'ms';
|
||||
|
||||
form_basefilter.setFieldsValue({ latency: latencyButtonText });
|
||||
form_basefilter.setFieldsValue({ service: traceFilters.service });
|
||||
form_basefilter.setFieldsValue({ operation: traceFilters.operation });
|
||||
form_basefilter.setFieldsValue({ kind: traceFilters.kind });
|
||||
}, [traceFilters, form_basefilter]);
|
||||
|
||||
const onLatencyButtonClick = (): void => {
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const onLatencyModalApply = (values: Store): void => {
|
||||
setModalVisible(false);
|
||||
const { min, max } = values;
|
||||
updateTraceFilters({
|
||||
...traceFilters,
|
||||
latency: {
|
||||
min: min ? (parseInt(min) * 1000000).toString() : '',
|
||||
max: max ? (parseInt(max) * 1000000).toString() : '',
|
||||
},
|
||||
});
|
||||
|
||||
setLatencyFilterValues({ min, max });
|
||||
};
|
||||
|
||||
// For autocomplete
|
||||
//Setting value when autocomplete field is changed
|
||||
const onChangeTagKey = (data: string): void => {
|
||||
form.setFieldsValue({ tag_key: data });
|
||||
};
|
||||
|
||||
const dataSource = ['status:200'];
|
||||
const children = [];
|
||||
for (let i = 0; i < dataSource.length; i++) {
|
||||
children.push(
|
||||
<Option value={dataSource[i]} key={dataSource[i]}>
|
||||
{dataSource[i]}
|
||||
</Option>,
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
updateTraceFilters({
|
||||
service: '',
|
||||
operation: '',
|
||||
tags: [],
|
||||
latency: { min: '', max: '' },
|
||||
kind: '',
|
||||
});
|
||||
};
|
||||
}, [updateTraceFilters]);
|
||||
|
||||
const handleChangeSpanKind = (value = ''): void => {
|
||||
updateTraceFilters({ ...traceFilters, kind: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography>Filter Traces</Typography>
|
||||
<Form
|
||||
form={form_basefilter}
|
||||
layout="inline"
|
||||
onFinish={handleApplyFilterForm}
|
||||
initialValues={{ service: '', operation: '', latency: 'Latency' }}
|
||||
style={{ marginTop: 10, marginBottom: 10 }}
|
||||
>
|
||||
<FormItem rules={[{ required: true }]} name="service">
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: 180 }}
|
||||
onChange={handleChangeService}
|
||||
placeholder="Select Service"
|
||||
allowClear
|
||||
>
|
||||
{serviceList.map((s) => (
|
||||
<Option key={s} value={s}>
|
||||
{s}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="operation">
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: 180 }}
|
||||
onChange={handleChangeOperation}
|
||||
placeholder="Select Operation"
|
||||
allowClear
|
||||
>
|
||||
{operationList.map((item) => (
|
||||
<Option key={item} value={item}>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="latency">
|
||||
<Input
|
||||
style={{ width: 200 }}
|
||||
type="button"
|
||||
onClick={onLatencyButtonClick}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="spanKind">
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: 180 }}
|
||||
onChange={handleChangeSpanKind}
|
||||
placeholder="Select Span Kind"
|
||||
allowClear
|
||||
>
|
||||
{spanKindList.map((spanKind) => (
|
||||
<Option value={spanKind.value} key={spanKind.value}>
|
||||
{spanKind.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<FilterStateDisplay />
|
||||
|
||||
<InfoWrapper>Select Service to get Tag suggestions </InfoWrapper>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="inline"
|
||||
onFinish={onTagFormSubmit}
|
||||
initialValues={{ operator: 'equals' }}
|
||||
style={{ marginTop: 10, marginBottom: 10 }}
|
||||
>
|
||||
<FormItem rules={[{ required: true }]} name="tag_key">
|
||||
<AutoComplete
|
||||
options={tagKeyOptions.map((s) => {
|
||||
return { value: s.tagKeys };
|
||||
})}
|
||||
style={{ width: 200, textAlign: 'center' }}
|
||||
onChange={onChangeTagKey}
|
||||
filterOption={(inputValue, option): boolean =>
|
||||
option?.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
placeholder="Tag Key"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="operator">
|
||||
<Select style={{ width: 120, textAlign: 'center' }}>
|
||||
<Option value="equals">EQUAL</Option>
|
||||
<Option value="contains">CONTAINS</Option>
|
||||
<Option value="regex">REGEX</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem rules={[{ required: true }]} name="tag_value">
|
||||
<Input
|
||||
style={{ width: 160, textAlign: 'center' }}
|
||||
placeholder="Tag Value"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{' '}
|
||||
Apply Tag Filter{' '}
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
{modalVisible && (
|
||||
<LatencyModalForm
|
||||
onCreate={onLatencyModalApply}
|
||||
latencyFilterValues={latencyFilterValues}
|
||||
onCancel={(): void => {
|
||||
setModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: AppState,
|
||||
): { traceFilters: TraceFilters; globalTime: GlobalTime } => {
|
||||
return { traceFilters: state.traceFilters, globalTime: state.globalTime };
|
||||
};
|
||||
|
||||
export const TraceFilter = connect(mapStateToProps, {
|
||||
updateTraceFilters: updateTraceFilters,
|
||||
fetchTraces: fetchTraces,
|
||||
})(_TraceFilter);
|
@ -1,160 +0,0 @@
|
||||
import { Space, Table } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { fetchTraces, pushDStree, traceResponseNew } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
import { isOnboardingSkipped } from 'utils/app';
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const TraceHeader = styled.div`
|
||||
margin: 16px 0;
|
||||
`;
|
||||
|
||||
interface TraceListProps {
|
||||
traces: traceResponseNew;
|
||||
fetchTraces: Function;
|
||||
}
|
||||
|
||||
interface TableDataSourceItem {
|
||||
key: string;
|
||||
spanid: string;
|
||||
traceid: string;
|
||||
operationName: string;
|
||||
startTime: number;
|
||||
duration: number;
|
||||
service: string;
|
||||
}
|
||||
|
||||
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
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
props.fetchTraces();
|
||||
}, []);
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Start Time',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
sorter: (a: any, b: any) => a.startTime - b.startTime,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => moment(value).format('YYYY-MM-DD hh:mm:ss'),
|
||||
|
||||
// new Date() assumes input in milliseconds. Start Time stamp returned by druid api for span list is in ms
|
||||
},
|
||||
{
|
||||
title: 'Service',
|
||||
dataIndex: 'service',
|
||||
key: 'service',
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operationName',
|
||||
key: 'operationName',
|
||||
},
|
||||
{
|
||||
title: 'Duration (in ms)',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
sorter: (a: any, b: any) => a.duration - b.duration,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value / 1000000).toFixed(2),
|
||||
},
|
||||
];
|
||||
|
||||
const dataSource: TableDataSourceItem[] = [];
|
||||
|
||||
const renderTraces = () => {
|
||||
if (
|
||||
typeof props.traces[0] !== 'undefined' &&
|
||||
props.traces[0].events.length > 0
|
||||
) {
|
||||
props.traces[0].events.map(
|
||||
(item: (number | string | string[] | pushDStree[])[], index) => {
|
||||
if (
|
||||
typeof item[0] === 'number' &&
|
||||
typeof item[4] === 'string' &&
|
||||
typeof item[6] === 'string' &&
|
||||
typeof item[1] === 'string' &&
|
||||
typeof item[2] === 'string' &&
|
||||
typeof item[3] === 'string'
|
||||
)
|
||||
dataSource.push({
|
||||
startTime: item[0],
|
||||
operationName: item[4],
|
||||
duration: parseInt(item[6]),
|
||||
spanid: item[1],
|
||||
traceid: item[2],
|
||||
key: index.toString(),
|
||||
service: item[3],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
//antd table in typescript - https://codesandbox.io/s/react-typescript-669cv
|
||||
|
||||
return (
|
||||
<StyledTable
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
size="middle"
|
||||
rowClassName=""
|
||||
onRow={(record) => ({
|
||||
onClick: () => {
|
||||
history.push({
|
||||
pathname: ROUTES.TRACES + '/' + record.traceid,
|
||||
state: {
|
||||
spanId: record.spanid,
|
||||
},
|
||||
});
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (isOnboardingSkipped()) {
|
||||
return (
|
||||
<Space
|
||||
style={{ width: '100%', margin: '40px 0', justifyContent: 'center' }}
|
||||
>
|
||||
No spans found. Please add instrumentation (follow this
|
||||
<a
|
||||
href={'https://signoz.io/docs/instrumentation/overview'}
|
||||
target={'_blank'}
|
||||
style={{ marginLeft: 3 }}
|
||||
rel="noreferrer"
|
||||
>
|
||||
guide
|
||||
</a>
|
||||
)
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
return <div> No spans found for given filter!</div>;
|
||||
}
|
||||
}; // end of renderTraces
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TraceHeader>List of filtered spans</TraceHeader>
|
||||
<div>{renderTraces()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState): { traces: traceResponseNew } => {
|
||||
return { traces: state.traces };
|
||||
};
|
||||
|
||||
export const TraceList = connect(mapStateToProps, {
|
||||
fetchTraces: fetchTraces,
|
||||
})(_TraceList);
|
@ -14,8 +14,13 @@ import { Card } from './styles';
|
||||
|
||||
interface UsageExplorerProps {
|
||||
usageData: usageDataItem[];
|
||||
getUsageData: Function;
|
||||
getServicesList: Function;
|
||||
getUsageData: (
|
||||
minTime: number,
|
||||
maxTime: number,
|
||||
selectedInterval: any,
|
||||
selectedService: string,
|
||||
) => void;
|
||||
getServicesList: (time: GlobalTime) => void;
|
||||
globalTime: GlobalTime;
|
||||
servicesList: servicesListItem[];
|
||||
totalCount: number;
|
||||
@ -47,27 +52,30 @@ const interval = [
|
||||
},
|
||||
];
|
||||
|
||||
const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
const _UsageExplorer = (props: UsageExplorerProps): JSX.Element => {
|
||||
const [selectedTime, setSelectedTime] = useState(timeDaysOptions[1]);
|
||||
const [selectedInterval, setSelectedInterval] = useState(interval[2]);
|
||||
const [selectedService, setSelectedService] = useState<string>('');
|
||||
const { loading } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const {
|
||||
getServicesList,
|
||||
getUsageData,
|
||||
globalTime,
|
||||
servicesList,
|
||||
totalCount,
|
||||
usageData,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTime && selectedInterval) {
|
||||
const maxTime = new Date().getTime() * 1000000;
|
||||
const minTime = maxTime - selectedTime.value * 24 * 3600000 * 1000000;
|
||||
|
||||
props.getUsageData(
|
||||
minTime,
|
||||
maxTime,
|
||||
selectedInterval!.value,
|
||||
selectedService,
|
||||
);
|
||||
getUsageData(minTime, maxTime, selectedInterval.value, selectedService);
|
||||
}
|
||||
}, [selectedTime, selectedInterval, selectedService]);
|
||||
}, [selectedTime, selectedInterval, selectedService, getUsageData]);
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
@ -75,16 +83,16 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||
*/
|
||||
if (loading) {
|
||||
props.getServicesList(props.globalTime);
|
||||
getServicesList(globalTime);
|
||||
}
|
||||
}, [loading, props]);
|
||||
}, [loading, globalTime, getServicesList]);
|
||||
|
||||
const data = {
|
||||
labels: props.usageData.map((s) => new Date(s.timestamp / 1000000)),
|
||||
labels: usageData.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Span Count',
|
||||
data: props.usageData.map((s) => s.count),
|
||||
data: usageData.map((s) => s.count),
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
borderWidth: 2,
|
||||
@ -94,11 +102,10 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{/* PNOTE - TODO - Keep it in reponsive row column tab */}
|
||||
<Space style={{ marginTop: 40, marginLeft: 20 }}>
|
||||
<Space>
|
||||
<Select
|
||||
onSelect={(value, option) => {
|
||||
onSelect={(value): void => {
|
||||
setSelectedTime(
|
||||
timeDaysOptions.filter((item) => item.value == parseInt(value))[0],
|
||||
);
|
||||
@ -106,42 +113,48 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
value={selectedTime.label}
|
||||
>
|
||||
{timeDaysOptions.map(({ value, label }) => (
|
||||
<Option value={value}>{label}</Option>
|
||||
<Option key={value} value={value}>
|
||||
{label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
<Space>
|
||||
<Select
|
||||
onSelect={(value) => {
|
||||
onSelect={(value): void => {
|
||||
setSelectedInterval(
|
||||
interval.filter((item) => item!.value === parseInt(value))[0],
|
||||
interval.filter((item) => item.value === parseInt(value))[0],
|
||||
);
|
||||
}}
|
||||
value={selectedInterval!.label}
|
||||
value={selectedInterval.label}
|
||||
>
|
||||
{interval
|
||||
.filter((interval) => interval!.applicableOn.includes(selectedTime))
|
||||
.filter((interval) => interval.applicableOn.includes(selectedTime))
|
||||
.map((item) => (
|
||||
<Option value={item!.value}>{item!.label}</Option>
|
||||
<Option key={item.label} value={item.value}>
|
||||
{item.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
|
||||
<Space>
|
||||
<Select
|
||||
onSelect={(value) => {
|
||||
onSelect={(value): void => {
|
||||
setSelectedService(value);
|
||||
}}
|
||||
value={selectedService || 'All Services'}
|
||||
>
|
||||
<Option value={''}>All Services</Option>
|
||||
{props.servicesList.map((service) => (
|
||||
<Option value={service.serviceName}>{service.serviceName}</Option>
|
||||
{servicesList.map((service) => (
|
||||
<Option key={service.serviceName} value={service.serviceName}>
|
||||
{service.serviceName}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
|
||||
{isOnboardingSkipped() && props.totalCount === 0 ? (
|
||||
{isOnboardingSkipped() && totalCount === 0 ? (
|
||||
<Space
|
||||
style={{
|
||||
width: '100%',
|
||||
@ -163,7 +176,7 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
||||
</Space>
|
||||
) : (
|
||||
<Space style={{ display: 'block', marginLeft: 20, width: 200 }}>
|
||||
{`Total count is ${props.totalCount}`}
|
||||
{`Total count is ${totalCount}`}
|
||||
</Space>
|
||||
)}
|
||||
</Space>
|
||||
@ -191,7 +204,7 @@ const mapStateToProps = (
|
||||
totalCount: totalCount,
|
||||
usageData: state.usageDate,
|
||||
globalTime: state.globalTime,
|
||||
servicesList: state.metricsData.serviceList,
|
||||
servicesList: state.metricsData.serviceList || [],
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Typography } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import MetricsApplicationContainer from 'container/MetricsApplication';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { bindActionCreators } from 'redux';
|
||||
@ -38,10 +38,10 @@ const MetricsApplication = ({
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
return (): void => {
|
||||
resetInitialData();
|
||||
};
|
||||
}, [servicename, getInitialData, selectedTime]);
|
||||
}, [servicename, getInitialData, selectedTime, resetInitialData]);
|
||||
|
||||
if (metricsApplicationLoading) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
|
@ -9,8 +9,8 @@ import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
GetInitialTraceData,
|
||||
ResetRaceData,
|
||||
GetInitialTraceDataProps,
|
||||
ResetRaceData,
|
||||
} from 'store/actions/trace';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
@ -40,7 +40,7 @@ const TraceDetail = ({
|
||||
return (): void => {
|
||||
resetTraceData();
|
||||
};
|
||||
}, [getInitialTraceData, loading, selectedTime]);
|
||||
}, [getInitialTraceData, loading, selectedTime, resetTraceData]);
|
||||
|
||||
if (error) {
|
||||
return <Typography>{errorMessage}</Typography>;
|
||||
|
@ -6,7 +6,7 @@ export const ResetInitialData = (): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
getState: () => AppState,
|
||||
) => void) => {
|
||||
return (dispatch, getState): void => {
|
||||
return (dispatch): void => {
|
||||
dispatch({
|
||||
type: 'RESET_INITIAL_APPLICATION_DATA',
|
||||
});
|
||||
|
@ -38,7 +38,7 @@ export interface servicesAction {
|
||||
}
|
||||
|
||||
export const getServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
return async (dispatch: Dispatch): Promise<void> => {
|
||||
dispatch<serviceMapItemAction>({
|
||||
type: ActionTypes.getServiceMapItems,
|
||||
payload: [],
|
||||
@ -60,7 +60,7 @@ export const getServiceMapItems = (globalTime: GlobalTime) => {
|
||||
};
|
||||
|
||||
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
return async (dispatch: Dispatch): Promise<void> => {
|
||||
dispatch<servicesAction>({
|
||||
type: ActionTypes.getServices,
|
||||
payload: [],
|
||||
|
@ -1,4 +1,5 @@
|
||||
export * from './getInitialData';
|
||||
export * from './resetTraceDetails';
|
||||
export * from './updateSelectedAggOption';
|
||||
export * from './updateSelectedEntity';
|
||||
export * from './updateSelectedKind';
|
||||
@ -6,4 +7,3 @@ export * from './updateSelectedLatency';
|
||||
export * from './updateSelectedOperation';
|
||||
export * from './updateSelectedService';
|
||||
export * from './updateSelectedTags';
|
||||
export * from './resetTraceDetails';
|
||||
|
@ -26,7 +26,9 @@ export interface updateTraceFiltersAction {
|
||||
payload: TraceFilters;
|
||||
}
|
||||
|
||||
export const updateTraceFilters = (traceFilters: TraceFilters) => {
|
||||
export const updateTraceFilters = (
|
||||
traceFilters: TraceFilters,
|
||||
): updateTraceFiltersAction => {
|
||||
return {
|
||||
type: ActionTypes.updateTraceFilters,
|
||||
payload: traceFilters,
|
||||
|
@ -20,7 +20,7 @@ export const getUsageData = (
|
||||
step: number,
|
||||
service: string,
|
||||
) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
return async (dispatch: Dispatch): Promise<void> => {
|
||||
const request_string = `/usage?start=${toUTCEpoch(minTime)}&end=${toUTCEpoch(
|
||||
maxTime,
|
||||
)}&step=${step}&service=${service ? service : ''}`;
|
||||
|
@ -4,7 +4,6 @@ import appReducer from './app';
|
||||
import dashboardReducer from './dashboard';
|
||||
import globalTimeReducer from './global';
|
||||
import metricsReducers from './metric';
|
||||
import { metricsReducer } from './metrics';
|
||||
import { ServiceMapReducer } from './serviceMap';
|
||||
import { traceReducer } from './trace';
|
||||
import TraceFilterReducer from './traceFilters';
|
||||
@ -18,7 +17,6 @@ const reducers = combineReducers({
|
||||
trace: traceReducer,
|
||||
usageDate: usageDataReducer,
|
||||
globalTime: globalTimeReducer,
|
||||
metricsData: metricsReducer,
|
||||
serviceMap: ServiceMapReducer,
|
||||
dashboards: dashboardReducer,
|
||||
app: appReducer,
|
||||
|
@ -5,8 +5,8 @@ import {
|
||||
GET_SERVICE_LIST_ERROR,
|
||||
GET_SERVICE_LIST_LOADING_START,
|
||||
GET_SERVICE_LIST_SUCCESS,
|
||||
RESET_INITIAL_APPLICATION_DATA,
|
||||
MetricsActions,
|
||||
RESET_INITIAL_APPLICATION_DATA,
|
||||
} from 'types/actions/metrics';
|
||||
import InitialValueTypes from 'types/reducer/metrics';
|
||||
|
||||
|
@ -1,112 +0,0 @@
|
||||
import {
|
||||
customMetricsItem,
|
||||
dbOverviewMetricsItem,
|
||||
externalErrCodeMetricsItem,
|
||||
externalMetricsAvgDurationItem,
|
||||
externalMetricsItem,
|
||||
metricItem,
|
||||
servicesListItem,
|
||||
topEndpointListItem,
|
||||
} from 'store/actions/MetricsActions';
|
||||
import { MetricsActionTypes as ActionTypes } from 'store/actions/MetricsActions/metricsActionTypes';
|
||||
|
||||
export type MetricsInitialState = {
|
||||
serviceList?: servicesListItem[];
|
||||
metricItems?: metricItem[];
|
||||
topEndpointListItem?: topEndpointListItem[];
|
||||
externalMetricsAvgDurationItem?: externalMetricsAvgDurationItem[];
|
||||
externalErrCodeMetricsItem?: externalErrCodeMetricsItem[];
|
||||
externalMetricsItem?: externalMetricsItem[];
|
||||
dbOverviewMetricsItem?: dbOverviewMetricsItem[];
|
||||
customMetricsItem?: customMetricsItem[];
|
||||
loading: boolean;
|
||||
};
|
||||
export const metricsInitialState: MetricsInitialState = {
|
||||
serviceList: [],
|
||||
metricItems: [],
|
||||
topEndpointListItem: [],
|
||||
externalMetricsAvgDurationItem: [],
|
||||
externalErrCodeMetricsItem: [],
|
||||
externalMetricsItem: [],
|
||||
dbOverviewMetricsItem: [],
|
||||
customMetricsItem: [],
|
||||
loading: false,
|
||||
};
|
||||
|
||||
type ActionType = {
|
||||
type: string;
|
||||
payload: any;
|
||||
};
|
||||
|
||||
export const metricsReducer = (
|
||||
state: MetricsInitialState = metricsInitialState,
|
||||
action: ActionType,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getFilteredTraceMetrics:
|
||||
return {
|
||||
...state,
|
||||
customMetricsItem: action.payload,
|
||||
};
|
||||
case ActionTypes.getServiceMetrics:
|
||||
return {
|
||||
...state,
|
||||
metricItems: action.payload,
|
||||
};
|
||||
case ActionTypes.getDbOverviewMetrics:
|
||||
return {
|
||||
...state,
|
||||
dbOverviewMetricsItem: action.payload,
|
||||
};
|
||||
case ActionTypes.getExternalMetrics:
|
||||
return {
|
||||
...state,
|
||||
externalMetricsItem: action.payload,
|
||||
};
|
||||
case ActionTypes.getTopEndpoints:
|
||||
return {
|
||||
...state,
|
||||
topEndpointListItem: action.payload,
|
||||
};
|
||||
case ActionTypes.getErrCodeMetrics:
|
||||
return {
|
||||
...state,
|
||||
externalErrCodeMetricsItem: action.payload,
|
||||
};
|
||||
case ActionTypes.getAvgDurationMetrics:
|
||||
return {
|
||||
...state,
|
||||
externalMetricsAvgDurationItem: action.payload,
|
||||
};
|
||||
|
||||
case ActionTypes.getServicesList:
|
||||
return {
|
||||
...state,
|
||||
serviceList: action.payload,
|
||||
};
|
||||
|
||||
case 'UPDATE_INITIAL_VALUE_START': {
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
};
|
||||
}
|
||||
|
||||
case 'UPDATE_INITIAL_VALUE': {
|
||||
return {
|
||||
...state,
|
||||
dbOverviewMetricsItem: action.payload.dbResponse,
|
||||
topEndpointListItem: action.payload.topEndPointsResponse,
|
||||
externalMetricsAvgDurationItem: action.payload.avgExternalDurationResponse,
|
||||
externalErrCodeMetricsItem: action.payload.externalErrorCodeMetricsResponse,
|
||||
metricItems: action.payload.serviceOverViewResponse,
|
||||
externalMetricsItem: action.payload.externalServiceResponse,
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
};
|
@ -5,7 +5,10 @@ const initialState: serviceMapStore = {
|
||||
services: [],
|
||||
};
|
||||
|
||||
export const ServiceMapReducer = (state = initialState, action: Action) => {
|
||||
export const ServiceMapReducer = (
|
||||
state = initialState,
|
||||
action: Action,
|
||||
): serviceMapStore => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getServiceMapItems:
|
||||
return {
|
||||
|
@ -3,7 +3,9 @@ import {
|
||||
GET_TRACE_INITIAL_DATA_SUCCESS,
|
||||
GET_TRACE_LOADING_END,
|
||||
GET_TRACE_LOADING_START,
|
||||
RESET_TRACE_DATA,
|
||||
TraceActions,
|
||||
UPDATE_AGGREGATES,
|
||||
UPDATE_SELECTED_AGG_OPTION,
|
||||
UPDATE_SELECTED_ENTITY,
|
||||
UPDATE_SELECTED_TRACE_DATA,
|
||||
@ -13,8 +15,6 @@ import {
|
||||
UPDATE_TRACE_SELECTED_OPERATION,
|
||||
UPDATE_TRACE_SELECTED_SERVICE,
|
||||
UPDATE_TRACE_SELECTED_TAGS,
|
||||
RESET_TRACE_DATA,
|
||||
UPDATE_AGGREGATES,
|
||||
} from 'types/actions/trace';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
|
@ -14,7 +14,10 @@ const initialState: TraceFilters = {
|
||||
kind: '',
|
||||
};
|
||||
|
||||
const TraceFilterReducer = (state = initialState, action: ACTION) => {
|
||||
const TraceFilterReducer = (
|
||||
state = initialState,
|
||||
action: ACTION,
|
||||
): TraceFilters => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.updateTraceFilters:
|
||||
return action.payload;
|
||||
|
@ -11,7 +11,7 @@ const spanlistinstance: spanList = { events: [], segmentID: '', columns: [] };
|
||||
export const tracesReducer = (
|
||||
state: traceResponseNew = { '0': spanlistinstance },
|
||||
action: Action,
|
||||
) => {
|
||||
): traceResponseNew => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.fetchTraces:
|
||||
return action.payload;
|
||||
@ -23,7 +23,7 @@ export const tracesReducer = (
|
||||
export const traceItemReducer = (
|
||||
state: spansWSameTraceIDResponse = { '0': spanlistinstance },
|
||||
action: Action,
|
||||
) => {
|
||||
): spansWSameTraceIDResponse => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.fetchTraceItem:
|
||||
return action.payload;
|
||||
|
@ -3,7 +3,7 @@ import { Action, ActionTypes, usageDataItem } from 'store/actions';
|
||||
export const usageDataReducer = (
|
||||
state: usageDataItem[] = [{ timestamp: 0, count: 0 }],
|
||||
action: Action,
|
||||
) => {
|
||||
): usageDataItem[] => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.getUsageData:
|
||||
return action.payload;
|
||||
|
@ -34,6 +34,6 @@ declare module 'react-graph-vis' {
|
||||
NetworkGraphProps,
|
||||
NetworkGraphState
|
||||
> {
|
||||
render();
|
||||
render(): JSX.Element;
|
||||
}
|
||||
}
|
||||
|
@ -2236,9 +2236,9 @@
|
||||
integrity sha512-09x2d6kNBwjHgyh3jOUE2GE4DFoxDriDvWdu6mFhMP1ysynGYazt4ecZmJlL6/fe4Zi2vtYvTvtL7epjQQrBhA==
|
||||
|
||||
"@types/node@^16.10.3":
|
||||
version "16.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5"
|
||||
integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==
|
||||
version "16.11.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.9.tgz#879be3ad7af29f4c1a5c433421bf99fab7047185"
|
||||
integrity sha512-MKmdASMf3LtPzwLyRrFjtFFZ48cMf8jmX5VRYrDQiJa8Ybu5VAmkqBWqKU8fdCwD8ysw4mQ9nrEHvzg6gunR7A==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user