mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-03 21:40:38 +08:00
Release/v0.7.0 (#814)
* feat(FE): dynamic step size of metrics page * chore(tests): migrate to dayjs for generating timestamp * bug: sorting of date is fixed * feat: soring filter is added * chore: typo is fixed * feat(backend): support custom events in span * fix: encode event string to fix parsing at frontend * chore: styles is updated for the not found button * chore: update otel-collector to 0.43.0 * fix: remove encoding * fix: set userId as distinctId if failed to fetch IP * Fe: Feat/trace detail (#764) * feat: new trace detail page flame graph * feat: new trace detail page layout * test: trace detail is wip * chore: trace details in wip * feat: trace detail page timeline component * chore: spantoTree is updated * chore: gantchart is updated * chore: onClick is added * chore: isSpanPresentInSearchString util is added * chore: trace graph is updated * chore: added the hack to work * feat: is span present util is added * chore: in span ms is added * chore: tooltip is updated * WIP: chore: trace details changes are updated * feat: getTraceItem is added * feat: trace detail page is updated * feat: trace detail styling changes * feat: trace detail page is updated * feat: implement span hover, select, focus and reset * feat: reset focus * feat: spanId as query table and unfurling * feat: trace details is updated * chore: remove lodash * chore: remove lodash * feat: trace details is updated * feat: new trace detail page styling changes * feat: new trace detail page styling changes * feat: improved styling * feat: remove horizontal scrolling * feat: new trace detail page modify caret icon * chore styles are updated * Revert "chore: Trace styles" * chore styles are updated * feat: timeline normalisation * chore: remove mock data * chore: sort tree data util is added and selected span component is updated * chore: trace changes are updated * chore: trace changes are updated * chore: trace changes are updated * feat: refactored time units for new trace detail page * chore: remove mockdata * feat: new trace detail page themeing and interval loop fix * chore: error tag is updated * chore: error tag is updated * chore: error tag is updated * chore: error tag is updated * chore: console is removed * fix: error tag expand button * chore: expanded panel is updated * feat: scroll span from gantt chart intoview * chore: trace detail is removed Co-authored-by: Pranshu Chittora <pranshu@signoz.io> * bug: Trace search bug is resolved (#741) * bug: Trace search bug is resolved * bug: Trace search bug is resolved * chore: parseTagsToQuery is updated * chore: parseTagsToQuery is updated * chore: parseTagsToQuery is updated * chore: parseTagsToQuery is updated * chore: ♿️ add hotrod template and install/delete scripts (#801) * chore: ♿️ add hotrod template and scripts Signed-off-by: Prashant Shahi <prashant@signoz.io> * refactor: ✨ conditionally compute image Signed-off-by: Prashant Shahi <prashant@signoz.io> * fix: 🩹 add signoz namespace Signed-off-by: Prashant Shahi <prashant@signoz.io> * chore: 🔨 fix namespace template in scripts Signed-off-by: Prashant Shahi <prashant@signoz.io> * docs(hotrod): 📝 Add README for hotrod k8s Signed-off-by: Prashant Shahi <prashant@signoz.io> Co-authored-by: Ankit Nayan <ankit@signoz.io> * chore(release): 📌 pin SigNoz and OtelCollector versions Signed-off-by: Prashant Shahi <prashant@signoz.io> Co-authored-by: Pranshu Chittora <pranshu@signoz.io> Co-authored-by: Palash gupta <palash@signoz.io> Co-authored-by: makeavish <makeavish786@gmail.com> Co-authored-by: Ankit Nayan <ankit@signoz.io>
This commit is contained in:
parent
abe4940e74
commit
eb63b6da2a
@ -23,7 +23,7 @@ services:
|
||||
- '--storage.path=/data'
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.6.2
|
||||
image: signoz/query-service:0.7.0
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@ -40,7 +40,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.6.2
|
||||
image: signoz/frontend:0.7.0
|
||||
container_name: frontend
|
||||
depends_on:
|
||||
- query-service
|
||||
@ -50,8 +50,8 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.6.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
ports:
|
||||
@ -63,8 +63,8 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.6.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
depends_on:
|
||||
|
@ -26,7 +26,7 @@ services:
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.6.2
|
||||
image: signoz/query-service:0.7.0
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@ -43,7 +43,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.6.2
|
||||
image: signoz/frontend:0.7.0
|
||||
container_name: frontend
|
||||
depends_on:
|
||||
- query-service
|
||||
@ -53,8 +53,8 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.6.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
ports:
|
||||
@ -66,8 +66,8 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.6.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
depends_on:
|
||||
|
@ -27,14 +27,14 @@ processors:
|
||||
signozspanmetrics/prometheus:
|
||||
metrics_exporter: prometheus
|
||||
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
|
||||
memory_limiter:
|
||||
# Same as --mem-ballast-size-mib CLI argument
|
||||
ballast_size_mib: 683
|
||||
# 80% of maximum memory up to 2G
|
||||
limit_mib: 1500
|
||||
# 25% of limit up to 2G
|
||||
spike_limit_mib: 512
|
||||
check_interval: 5s
|
||||
# memory_limiter:
|
||||
# # Same as --mem-ballast-size-mib CLI argument
|
||||
# ballast_size_mib: 683
|
||||
# # 80% of maximum memory up to 2G
|
||||
# limit_mib: 1500
|
||||
# # 25% of limit up to 2G
|
||||
# spike_limit_mib: 512
|
||||
# check_interval: 5s
|
||||
# queued_retry:
|
||||
# num_workers: 4
|
||||
# queue_size: 100
|
||||
|
@ -16,14 +16,14 @@ processors:
|
||||
batch:
|
||||
send_batch_size: 1000
|
||||
timeout: 10s
|
||||
memory_limiter:
|
||||
# Same as --mem-ballast-size-mib CLI argument
|
||||
ballast_size_mib: 683
|
||||
# 80% of maximum memory up to 2G
|
||||
limit_mib: 1500
|
||||
# 25% of limit up to 2G
|
||||
spike_limit_mib: 512
|
||||
check_interval: 5s
|
||||
# memory_limiter:
|
||||
# # Same as --mem-ballast-size-mib CLI argument
|
||||
# ballast_size_mib: 683
|
||||
# # 80% of maximum memory up to 2G
|
||||
# limit_mib: 1500
|
||||
# # 25% of limit up to 2G
|
||||
# spike_limit_mib: 512
|
||||
# check_interval: 5s
|
||||
# queued_retry:
|
||||
# num_workers: 4
|
||||
# queue_size: 100
|
||||
|
@ -36,6 +36,7 @@
|
||||
"babel-preset-react-app": "^10.0.0",
|
||||
"chart.js": "^3.4.0",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"color": "^4.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "4.3.0",
|
||||
"css-minimizer-webpack-plugin": "^3.2.0",
|
||||
@ -59,6 +60,7 @@
|
||||
"react-grid-layout": "^1.2.5",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use": "^17.3.2",
|
||||
"react-vis": "^1.11.7",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
@ -93,6 +95,7 @@
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@testing-library/cypress": "^8.0.0",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/compression-webpack-plugin": "^9.0.0",
|
||||
"@types/copy-webpack-plugin": "^8.0.1",
|
||||
"@types/d3": "^6.2.0",
|
||||
@ -138,6 +141,7 @@
|
||||
"prettier": "2.2.1",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.5.0"
|
||||
}
|
||||
|
@ -18,15 +18,12 @@ export const ServiceMapPage = Loadable(
|
||||
),
|
||||
);
|
||||
|
||||
export const TraceDetailPages = Loadable(
|
||||
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/Trace'),
|
||||
export const TraceFilter = Loadable(
|
||||
() => import(/* webpackChunkName: "Trace Filter Page" */ 'pages/Trace'),
|
||||
);
|
||||
|
||||
export const TraceGraphPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "TraceGraphPage" */ 'modules/Traces/TraceGraphDef'
|
||||
),
|
||||
export const TraceDetail = Loadable(
|
||||
() => import(/* webpackChunkName: "TraceDetail Page" */ 'pages/TraceDetail'),
|
||||
);
|
||||
|
||||
export const UsageExplorerPage = Loadable(
|
||||
|
@ -17,8 +17,8 @@ import {
|
||||
ServicesTablePage,
|
||||
SettingsPage,
|
||||
SignupPage,
|
||||
TraceDetailPages,
|
||||
TraceGraphPage,
|
||||
TraceFilter,
|
||||
TraceDetail,
|
||||
UsageExplorerPage,
|
||||
} from './pageComponents';
|
||||
|
||||
@ -44,9 +44,9 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_GRAPH,
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
component: TraceGraphPage,
|
||||
component: TraceDetail,
|
||||
},
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
@ -96,7 +96,7 @@ const routes: AppRoutes[] = [
|
||||
{
|
||||
path: ROUTES.TRACE,
|
||||
exact: true,
|
||||
component: TraceDetailPages,
|
||||
component: TraceFilter,
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_NEW,
|
||||
|
@ -15,6 +15,7 @@ const getSpanAggregate = async (
|
||||
end: String(props.end),
|
||||
limit: props.limit,
|
||||
offset: props.offset,
|
||||
order: props.order,
|
||||
};
|
||||
|
||||
const exclude: TraceFilterEnum[] = [];
|
||||
|
27
frontend/src/api/trace/getTraceItem.ts
Normal file
27
frontend/src/api/trace/getTraceItem.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Props, PayloadProps } from 'types/api/trace/getTraceItem';
|
||||
|
||||
const getTraceItem = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.request<PayloadProps>({
|
||||
url: `/traces/${props.id}`,
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTraceItem;
|
@ -2,7 +2,6 @@ import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Button = styled(Link)`
|
||||
height: 100%;
|
||||
border: 2px solid #2f80ed;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
|
@ -3,7 +3,7 @@ const ROUTES = {
|
||||
SERVICE_METRICS: '/application/:servicename',
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACE: '/trace',
|
||||
TRACE_GRAPH: '/trace/:id',
|
||||
TRACE_DETAIL: '/trace/:id',
|
||||
SETTINGS: '/settings',
|
||||
INSTRUMENTATION: '/add-instrumentation',
|
||||
USAGE_EXPLORER: '/usage-explorer',
|
||||
|
@ -37,7 +37,7 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||
{!isSignUpPage && <TopNav />}
|
||||
{children}
|
||||
</Content>
|
||||
<Footer>{`SigNoz Inc. © ${currentYear}`}</Footer>
|
||||
{/* <Footer>{`SigNoz Inc. © ${currentYear}`}</Footer> */}
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
|
@ -12,6 +12,8 @@ export const Layout = styled(LayoutComponent)`
|
||||
export const Content = styled(LayoutComponent.Content)`
|
||||
&&& {
|
||||
margin: 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`;
|
||||
|
||||
|
29
frontend/src/container/GantChart/SpanLength/index.tsx
Normal file
29
frontend/src/container/GantChart/SpanLength/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Tooltip, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { SpanBorder, SpanText, SpanWrapper, SpanLine } from './styles';
|
||||
import { toFixed } from 'utils/toFixed'
|
||||
import { IIntervalUnit, resolveTimeFromInterval } from 'container/TraceDetail/utils';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
interface SpanLengthProps {
|
||||
width: string;
|
||||
leftOffset: string;
|
||||
bgColor: string;
|
||||
toolTipText: string;
|
||||
id: string;
|
||||
inMsCount: number;
|
||||
intervalUnit: IIntervalUnit;
|
||||
}
|
||||
|
||||
const SpanLength = (props: SpanLengthProps): JSX.Element => {
|
||||
const { width, leftOffset, bgColor, intervalUnit } = props;
|
||||
const { isDarkMode } = useThemeMode()
|
||||
return (
|
||||
<SpanWrapper>
|
||||
<SpanLine leftOffset={leftOffset} isDarkMode={isDarkMode} />
|
||||
<SpanBorder bgColor={bgColor} leftOffset={leftOffset} width={width} />
|
||||
<SpanText leftOffset={leftOffset} isDarkMode={isDarkMode}>{`${toFixed(resolveTimeFromInterval(props.inMsCount, intervalUnit), 2)} ${intervalUnit.name}`}</SpanText>
|
||||
</SpanWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpanLength;
|
57
frontend/src/container/GantChart/SpanLength/styles.ts
Normal file
57
frontend/src/container/GantChart/SpanLength/styles.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
width: string;
|
||||
leftOffset: string;
|
||||
bgColor: string;
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const SpanLine = styled.div<Props>`
|
||||
width: ${({ leftOffset }) => `${leftOffset}%`};
|
||||
height: 0px;
|
||||
border-bottom: 0.1px solid
|
||||
${({ isDarkMode }) => (isDarkMode ? '#303030' : '#c0c0c0')};
|
||||
top: 50%;
|
||||
position: absolute;
|
||||
`;
|
||||
export const SpanBorder = styled.div<Props>`
|
||||
background: ${({ bgColor }) => bgColor};
|
||||
border-radius: 5px;
|
||||
height: 0.625rem;
|
||||
width: ${({ width }) => `${width}%`};
|
||||
left: ${({ leftOffset }) => `${leftOffset}%`};
|
||||
top: 35%;
|
||||
position: absolute;
|
||||
`;
|
||||
|
||||
export const SpanWrapper = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 2rem;
|
||||
|
||||
/* &:before {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
border-bottom: 1px solid #303030;
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
width: 30px;
|
||||
z-index: 0;
|
||||
} */
|
||||
`;
|
||||
|
||||
export const SpanText = styled(Typography)<Pick<Props, 'leftOffset'>>`
|
||||
&&& {
|
||||
left: ${({ leftOffset }) => `${leftOffset}%`};
|
||||
top: 65%;
|
||||
position: absolute;
|
||||
color: ${({ isDarkMode }) => (isDarkMode ? '##ACACAC' : '#666')};
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
`;
|
30
frontend/src/container/GantChart/SpanName/index.tsx
Normal file
30
frontend/src/container/GantChart/SpanName/index.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Service,
|
||||
Span,
|
||||
SpanWrapper,
|
||||
SpanConnector,
|
||||
Container,
|
||||
SpanName,
|
||||
} from './styles';
|
||||
|
||||
const SpanNameComponent = ({
|
||||
name,
|
||||
serviceName,
|
||||
}: SpanNameComponent): JSX.Element => {
|
||||
return (
|
||||
<Container title={`${name} ${serviceName}`}>
|
||||
<SpanWrapper>
|
||||
<Span ellipsis>{name}</Span>
|
||||
<Service>{serviceName}</Service>
|
||||
</SpanWrapper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
interface SpanNameComponent {
|
||||
name: string;
|
||||
serviceName: string;
|
||||
}
|
||||
|
||||
export default SpanNameComponent;
|
41
frontend/src/container/GantChart/SpanName/styles.ts
Normal file
41
frontend/src/container/GantChart/SpanName/styles.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import styled from 'styled-components';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
export const Span = styled(Typography.Paragraph)`
|
||||
&&& {
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
/* border-bottom: 1px solid grey; */
|
||||
}
|
||||
`;
|
||||
|
||||
export const Service = styled(Typography)`
|
||||
&&& {
|
||||
color: #acacac;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SpanWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 0.625rem;
|
||||
width: 10rem;
|
||||
`;
|
||||
|
||||
export const SpanConnector = styled.div`
|
||||
width: 37px;
|
||||
border: 1px solid #303030;
|
||||
height: 0;
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
export const SpanName = styled.div`
|
||||
width: fit-content;
|
||||
border-bottom: 1px solid black;
|
||||
`;
|
186
frontend/src/container/GantChart/Trace/index.tsx
Normal file
186
frontend/src/container/GantChart/Trace/index.tsx
Normal file
@ -0,0 +1,186 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
CardComponent,
|
||||
CardContainer,
|
||||
CaretContainer,
|
||||
Wrapper,
|
||||
HoverCard,
|
||||
} from './styles';
|
||||
import { CaretDownFilled, CaretRightFilled } from '@ant-design/icons';
|
||||
import SpanLength from '../SpanLength';
|
||||
import SpanName from '../SpanName';
|
||||
import { pushDStree } from 'store/actions';
|
||||
import { getMetaDataFromSpanTree, getTopLeftFromBody } from '../utils';
|
||||
import { ITraceMetaData } from '..';
|
||||
import { Col, Row } from 'antd';
|
||||
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants'
|
||||
import { IIntervalUnit, resolveTimeFromInterval } from 'container/TraceDetail/utils';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
|
||||
const Trace = (props: TraceProps): JSX.Element => {
|
||||
const {
|
||||
name,
|
||||
activeHoverId,
|
||||
setActiveHoverId,
|
||||
globalSpread,
|
||||
globalStart,
|
||||
serviceName,
|
||||
startTime,
|
||||
value,
|
||||
serviceColour,
|
||||
id,
|
||||
setActiveSelectedId,
|
||||
activeSelectedId,
|
||||
level,
|
||||
activeSpanPath,
|
||||
isExpandAll,
|
||||
intervalUnit,
|
||||
} = props;
|
||||
|
||||
const { isDarkMode } = useThemeMode()
|
||||
const [isOpen, setOpen] = useState<boolean>(activeSpanPath[level] === id);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setOpen(activeSpanPath[level] === id)
|
||||
}
|
||||
}, [activeSpanPath, isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpandAll) {
|
||||
setOpen(isExpandAll)
|
||||
}
|
||||
else {
|
||||
setOpen(activeSpanPath[level] === id)
|
||||
}
|
||||
}, [isExpandAll])
|
||||
|
||||
const isOnlyChild = props.children.length === 1;
|
||||
const [top, setTop] = useState<number>(0);
|
||||
|
||||
const ref = useRef<HTMLUListElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (activeSelectedId === id) {
|
||||
ref.current?.scrollIntoView({ block: 'nearest', behavior: 'auto', inline: 'nearest' });
|
||||
}
|
||||
}, [activeSelectedId])
|
||||
|
||||
const onMouseEnterHandler = () => {
|
||||
setActiveHoverId(props.id);
|
||||
if (ref.current) {
|
||||
const { top } = getTopLeftFromBody(ref.current);
|
||||
setTop(top);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseLeaveHandler = () => {
|
||||
setActiveHoverId('');
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
setActiveSelectedId(id);
|
||||
}
|
||||
const { totalSpans } = getMetaDataFromSpanTree(props);
|
||||
|
||||
const inMsCount = value;
|
||||
const nodeLeftOffset = ((startTime - globalStart) * 1e2) / globalSpread;
|
||||
const width = (value * 1e2) / (globalSpread * 1e6);
|
||||
const panelWidth = SPAN_DETAILS_LEFT_COL_WIDTH - (level * (16 + 1)) - 16;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper
|
||||
onMouseEnter={onMouseEnterHandler}
|
||||
onMouseLeave={onMouseLeaveHandler}
|
||||
isOnlyChild={isOnlyChild}
|
||||
ref={ref}
|
||||
>
|
||||
<HoverCard
|
||||
top={top}
|
||||
isHovered={activeHoverId === id}
|
||||
isSelected={activeSelectedId === id}
|
||||
isDarkMode={isDarkMode}
|
||||
/>
|
||||
|
||||
<CardContainer
|
||||
onClick={onClick}
|
||||
>
|
||||
<Col flex={`${panelWidth}px`} style={{ overflow: 'hidden' }}>
|
||||
<Row style={{ flexWrap: 'nowrap' }}>
|
||||
<Col>
|
||||
{totalSpans !== 1 && (
|
||||
<CardComponent
|
||||
isDarkMode={isDarkMode}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen((state) => !state);
|
||||
}}
|
||||
>
|
||||
{totalSpans}
|
||||
<CaretContainer>
|
||||
{isOpen ? <CaretDownFilled /> : <CaretRightFilled />}
|
||||
</CaretContainer>
|
||||
</CardComponent>
|
||||
)}
|
||||
</Col>
|
||||
<Col>
|
||||
<SpanName name={name} serviceName={serviceName} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col flex={'1'} >
|
||||
<SpanLength
|
||||
leftOffset={nodeLeftOffset.toString()}
|
||||
width={width.toString()}
|
||||
bgColor={serviceColour}
|
||||
id={id}
|
||||
inMsCount={(inMsCount / 1e6)}
|
||||
intervalUnit={intervalUnit}
|
||||
/>
|
||||
</Col>
|
||||
</CardContainer>
|
||||
|
||||
{isOpen && (
|
||||
<>
|
||||
{props.children.map((child) => (
|
||||
<Trace
|
||||
key={child.id}
|
||||
activeHoverId={props.activeHoverId}
|
||||
setActiveHoverId={props.setActiveHoverId}
|
||||
{...child}
|
||||
globalSpread={globalSpread}
|
||||
globalStart={globalStart}
|
||||
setActiveSelectedId={setActiveSelectedId}
|
||||
activeSelectedId={activeSelectedId}
|
||||
level={level + 1}
|
||||
activeSpanPath={activeSpanPath}
|
||||
isExpandAll={isExpandAll}
|
||||
intervalUnit={intervalUnit}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface ITraceGlobal {
|
||||
globalSpread: ITraceMetaData['spread'];
|
||||
globalStart: ITraceMetaData['globalStart'];
|
||||
}
|
||||
|
||||
interface TraceProps extends pushDStree, ITraceGlobal {
|
||||
activeHoverId: string;
|
||||
setActiveHoverId: React.Dispatch<React.SetStateAction<string>>;
|
||||
setActiveSelectedId: React.Dispatch<React.SetStateAction<string>>;
|
||||
activeSelectedId: string;
|
||||
level: number;
|
||||
activeSpanPath: string[];
|
||||
isExpandAll: boolean;
|
||||
intervalUnit: IIntervalUnit;
|
||||
}
|
||||
|
||||
export default Trace;
|
77
frontend/src/container/GantChart/Trace/styles.ts
Normal file
77
frontend/src/container/GantChart/Trace/styles.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
isOnlyChild: boolean;
|
||||
}
|
||||
|
||||
export const Wrapper = styled.ul<Props>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
ul {
|
||||
border-left: ${({ isOnlyChild }) => isOnlyChild && 'none'} !important;
|
||||
|
||||
${({ isOnlyChild }) =>
|
||||
isOnlyChild &&
|
||||
css`
|
||||
&:before {
|
||||
border-left: 1px solid #434343;
|
||||
display: inline-block;
|
||||
content: '';
|
||||
height: 54px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -35px;
|
||||
}
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardContainer = styled.li`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const CardComponent = styled.div`
|
||||
border: 1px solid ${({ isDarkMode }) => (isDarkMode ? '#434343' : '#333')};
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1px 8px;
|
||||
background: ${({ isDarkMode }) => (isDarkMode ? '#1d1d1d' : '#ddd')};
|
||||
height: 22px;
|
||||
`;
|
||||
|
||||
export const CaretContainer = styled.span`
|
||||
margin-left: 0.304rem;
|
||||
`;
|
||||
|
||||
interface HoverCardProps {
|
||||
isHovered: boolean;
|
||||
isSelected: boolean;
|
||||
top: number;
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const HoverCard = styled.div<HoverCardProps>`
|
||||
display: ${({ isSelected, isHovered }) =>
|
||||
isSelected || isHovered ? 'block' : 'none'};
|
||||
width: 200%;
|
||||
background-color: ${({ isHovered, isDarkMode }) =>
|
||||
isHovered && (isDarkMode ? '#262626' : '#ddd')};
|
||||
background-color: ${({ isSelected, isDarkMode }) =>
|
||||
isSelected && (isDarkMode ? '#4f4f4f' : '#bbb')};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
right: 0;
|
||||
height: 3rem;
|
||||
opacity: 0.5;
|
||||
`;
|
87
frontend/src/container/GantChart/index.tsx
Normal file
87
frontend/src/container/GantChart/index.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Trace from './Trace';
|
||||
import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons'
|
||||
import { Wrapper, CardWrapper, CardContainer, CollapseButton } from './styles';
|
||||
import { getSpanPath } from './utils';
|
||||
import { IIntervalUnit } from 'container/TraceDetail/utils'
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
|
||||
const GanttChart = (props: GanttChartProps): JSX.Element => {
|
||||
const {
|
||||
data,
|
||||
traceMetaData,
|
||||
activeHoverId,
|
||||
setActiveHoverId,
|
||||
activeSelectedId,
|
||||
setActiveSelectedId,
|
||||
spanId,
|
||||
intervalUnit
|
||||
} = props;
|
||||
|
||||
const { globalStart, spread: globalSpread } = traceMetaData;
|
||||
|
||||
const [isExpandAll, setIsExpandAll] = useState<boolean>(false);
|
||||
const [activeSpanPath, setActiveSpanPath] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveSpanPath(getSpanPath(data, spanId))
|
||||
}, [spanId]);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveSpanPath(getSpanPath(data, activeSelectedId))
|
||||
}, [activeSelectedId]);
|
||||
|
||||
const handleCollapse = () => {
|
||||
setIsExpandAll((prev) => !prev);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
<CardContainer>
|
||||
<CollapseButton onClick={handleCollapse} style={{ fontSize: '1.2rem' }} title={isExpandAll ? 'Collapse All' : "Expand All"}>
|
||||
{isExpandAll ? <MinusSquareOutlined /> : <PlusSquareOutlined />}
|
||||
</CollapseButton>
|
||||
<CardWrapper>
|
||||
<Trace
|
||||
activeHoverId={activeHoverId}
|
||||
activeSpanPath={activeSpanPath}
|
||||
setActiveHoverId={setActiveHoverId}
|
||||
key={data.id}
|
||||
{...{
|
||||
...data,
|
||||
globalSpread,
|
||||
globalStart,
|
||||
setActiveSelectedId,
|
||||
activeSelectedId,
|
||||
}}
|
||||
level={0}
|
||||
isExpandAll={isExpandAll}
|
||||
intervalUnit={intervalUnit}
|
||||
/>
|
||||
</CardWrapper>
|
||||
</CardContainer>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface ITraceMetaData {
|
||||
globalEnd: number;
|
||||
globalStart: number;
|
||||
levels: number;
|
||||
spread: number;
|
||||
totalSpans: number;
|
||||
}
|
||||
|
||||
export interface GanttChartProps {
|
||||
data: ITraceTree;
|
||||
traceMetaData: ITraceMetaData;
|
||||
activeSelectedId: string;
|
||||
activeHoverId: string;
|
||||
setActiveHoverId: React.Dispatch<React.SetStateAction<string>>;
|
||||
setActiveSelectedId: React.Dispatch<React.SetStateAction<string>>;
|
||||
spanId: string;
|
||||
intervalUnit: IIntervalUnit
|
||||
}
|
||||
|
||||
export default GanttChart;
|
47
frontend/src/container/GantChart/styles.ts
Normal file
47
frontend/src/container/GantChart/styles.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Wrapper = styled.ul`
|
||||
padding-left: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
border-left: 1px solid #434343;
|
||||
padding-left: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ul li {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: -1rem;
|
||||
top: 10px;
|
||||
content: '';
|
||||
height: 1px;
|
||||
width: 1rem;
|
||||
background-color: #434343;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardWrapper = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-left: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
`;
|
||||
|
||||
export const CardContainer = styled.li`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const CollapseButton = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
`;
|
165
frontend/src/container/GantChart/utils.ts
Normal file
165
frontend/src/container/GantChart/utils.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
|
||||
export const getMetaDataFromSpanTree = (treeData: ITraceTree) => {
|
||||
let globalStart = Number.POSITIVE_INFINITY;
|
||||
let globalEnd = Number.NEGATIVE_INFINITY;
|
||||
let totalSpans = 0;
|
||||
let levels = 1;
|
||||
const traverse = (treeNode: ITraceTree, level: number = 0) => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
totalSpans++;
|
||||
levels = Math.max(levels, level);
|
||||
const startTime = treeNode.startTime;
|
||||
const endTime = startTime + treeNode.value;
|
||||
globalStart = Math.min(globalStart, startTime);
|
||||
globalEnd = Math.max(globalEnd, endTime);
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
traverse(childNode, level + 1);
|
||||
}
|
||||
};
|
||||
traverse(treeData, 1);
|
||||
|
||||
globalStart = globalStart * 1e6;
|
||||
globalEnd = globalEnd * 1e6;
|
||||
|
||||
return {
|
||||
globalStart,
|
||||
globalEnd,
|
||||
spread: globalEnd - globalStart,
|
||||
totalSpans,
|
||||
levels,
|
||||
};
|
||||
};
|
||||
|
||||
export function getTopLeftFromBody(elem: HTMLElement) {
|
||||
let box = elem.getBoundingClientRect();
|
||||
|
||||
let body = document.body;
|
||||
let docEl = document.documentElement;
|
||||
|
||||
let scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
let scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
let clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
let clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
let top = box.top + scrollTop - clientTop;
|
||||
let left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: Math.round(top), left: Math.round(left) };
|
||||
}
|
||||
|
||||
export const getNodeById = (
|
||||
searchingId: string,
|
||||
treeData: ITraceTree,
|
||||
): ITraceTree | undefined => {
|
||||
let foundNode: ITraceTree | undefined = undefined;
|
||||
const traverse = (treeNode: ITraceTree, level: number = 0) => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchingId == treeNode.id) {
|
||||
foundNode = treeNode;
|
||||
}
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
traverse(childNode, level + 1);
|
||||
}
|
||||
};
|
||||
traverse(treeData, 1);
|
||||
|
||||
return foundNode;
|
||||
};
|
||||
|
||||
const getSpanWithoutChildren = (
|
||||
span: ITraceTree,
|
||||
): Omit<ITraceTree, 'children'> => {
|
||||
return {
|
||||
id: span.id,
|
||||
name: span.name,
|
||||
parent: span.parent,
|
||||
serviceColour: span.serviceColour,
|
||||
serviceName: span.serviceName,
|
||||
startTime: span.startTime,
|
||||
tags: span.tags,
|
||||
time: span.time,
|
||||
value: span.value,
|
||||
error: span.error,
|
||||
hasError: span.hasError,
|
||||
};
|
||||
};
|
||||
|
||||
export const isSpanPresentInSearchString = (
|
||||
searchedString: string,
|
||||
tree: ITraceTree,
|
||||
): boolean => {
|
||||
const parsedTree = getSpanWithoutChildren(tree);
|
||||
|
||||
const stringifyTree = JSON.stringify(parsedTree);
|
||||
|
||||
if (stringifyTree.includes(searchedString)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isSpanPresent = (
|
||||
tree: ITraceTree,
|
||||
searchedKey: string,
|
||||
): ITraceTree[] => {
|
||||
const foundNode: ITraceTree[] = [];
|
||||
|
||||
const traverse = (
|
||||
treeNode: ITraceTree,
|
||||
level: number = 0,
|
||||
foundNode: ITraceTree[],
|
||||
) => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isPresent = isSpanPresentInSearchString(searchedKey, treeNode);
|
||||
|
||||
if (isPresent) {
|
||||
foundNode.push(treeNode);
|
||||
}
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
traverse(childNode, level + 1, foundNode);
|
||||
}
|
||||
};
|
||||
traverse(tree, 1, foundNode);
|
||||
|
||||
return foundNode;
|
||||
};
|
||||
|
||||
export const getSpanPath = (tree: ITraceTree, spanId: string): string[] => {
|
||||
const spanPath: string[] = [];
|
||||
|
||||
const traverse = (treeNode: ITraceTree) => {
|
||||
if (!treeNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
spanPath.push(treeNode.id);
|
||||
|
||||
if (spanId === treeNode.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let foundInChild = false;
|
||||
for (const childNode of treeNode.children) {
|
||||
if (traverse(childNode)) foundInChild = true;
|
||||
}
|
||||
if (!foundInChild) {
|
||||
spanPath.pop();
|
||||
}
|
||||
return foundInChild;
|
||||
};
|
||||
traverse(tree);
|
||||
return spanPath;
|
||||
};
|
@ -16,6 +16,7 @@ import getChartData from 'lib/getChartData';
|
||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getStep from 'lib/getStep';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@ -90,7 +91,11 @@ const FullView = ({
|
||||
end: queryMinMax.max.toString(),
|
||||
query: query.query,
|
||||
start: queryMinMax.min.toString(),
|
||||
step: '60',
|
||||
step: `${getStep({
|
||||
start: queryMinMax.min,
|
||||
end: queryMinMax.max,
|
||||
inputFormat: 's',
|
||||
})}`,
|
||||
});
|
||||
return {
|
||||
query: query.query,
|
||||
|
@ -11,7 +11,7 @@ import { Container } from './styles';
|
||||
const routesToSkip = [
|
||||
ROUTES.SETTINGS,
|
||||
ROUTES.LIST_ALL_ALERT,
|
||||
ROUTES.TRACE_GRAPH,
|
||||
ROUTES.TRACE_DETAIL,
|
||||
];
|
||||
|
||||
const TopNav = (): JSX.Element | null => {
|
||||
|
@ -1,10 +1,5 @@
|
||||
import { Menu, Typography } from 'antd';
|
||||
import {
|
||||
MenuItem,
|
||||
SlackButton,
|
||||
SlackMenuItemContainer,
|
||||
ToggleButton,
|
||||
} from './styles';
|
||||
import { SlackButton, SlackMenuItemContainer, ToggleButton } from './styles';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
@ -17,7 +12,6 @@ import { ToggleDarkMode } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import getTheme from 'lib/theme/getTheme';
|
||||
import setTheme from 'lib/theme/setTheme';
|
||||
|
||||
import menus from './menuItems';
|
||||
@ -95,11 +89,10 @@ const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
|
||||
<Typography>{name}</Typography>
|
||||
</Menu.Item>
|
||||
))}
|
||||
|
||||
<SlackMenuItemContainer collapsed={collapsed}>
|
||||
<MenuItem onClick={onClickSlackHandler} icon={<Slack />}>
|
||||
<Menu.Item onClick={onClickSlackHandler} icon={<Slack />}>
|
||||
<SlackButton>Support</SlackButton>
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</SlackMenuItemContainer>
|
||||
</Menu>
|
||||
</Sider>
|
||||
|
@ -20,6 +20,7 @@ interface LogoProps {
|
||||
}
|
||||
|
||||
export const Sider = styled(SiderComponent)`
|
||||
z-index: 999;
|
||||
.ant-typography {
|
||||
color: white;
|
||||
}
|
||||
@ -45,17 +46,12 @@ export const SlackButton = styled(Typography)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const MenuItem = styled(Menu.Item)`
|
||||
&&& {
|
||||
position: fixed;
|
||||
bottom: 48px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: #262626;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SlackMenuItemContainer = styled.div<LogoProps>`
|
||||
position: fixed;
|
||||
bottom: 48px;
|
||||
background: #262626;
|
||||
width: ${({ collapsed }) => (!collapsed ? '200px' : '80px')};
|
||||
|
||||
&&& {
|
||||
li {
|
||||
${({ collapsed }) =>
|
||||
|
105
frontend/src/container/Timeline/index.tsx
Normal file
105
frontend/src/container/Timeline/index.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import styles from './style.module.css';
|
||||
import { useMeasure } from 'react-use';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
import {
|
||||
INTERVAL_UNITS,
|
||||
resolveTimeFromInterval,
|
||||
} from 'container/TraceDetail/utils';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
import { Interval } from './types';
|
||||
import { getIntervalSpread, getIntervals } from './utils';
|
||||
interface TimelineProps {
|
||||
traceMetaData: object;
|
||||
globalTraceMetadata: object;
|
||||
intervalUnit: object;
|
||||
setIntervalUnit: Function;
|
||||
}
|
||||
const Timeline = ({
|
||||
traceMetaData,
|
||||
globalTraceMetadata,
|
||||
intervalUnit,
|
||||
setIntervalUnit,
|
||||
}: TimelineProps) => {
|
||||
const [ref, { width }] = useMeasure<HTMLDivElement>();
|
||||
const { isDarkMode } = useThemeMode();
|
||||
|
||||
const Timeline_Height = 22;
|
||||
const Timeline_H_Spacing = 0;
|
||||
|
||||
const [intervals, setIntervals] = useState<Interval[] | null>(null);
|
||||
|
||||
useMemo(() => {
|
||||
const {
|
||||
baseInterval,
|
||||
baseSpread,
|
||||
intervalSpreadNormalized,
|
||||
} = getIntervalSpread({
|
||||
globalTraceMetadata: globalTraceMetadata,
|
||||
localTraceMetaData: traceMetaData,
|
||||
});
|
||||
|
||||
let intervalUnit = INTERVAL_UNITS[0];
|
||||
for (const idx in INTERVAL_UNITS) {
|
||||
const standard_interval = INTERVAL_UNITS[idx];
|
||||
if (baseSpread * standard_interval.multiplier < 1) {
|
||||
intervalUnit = INTERVAL_UNITS[idx - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setIntervalUnit(intervalUnit);
|
||||
setIntervals(
|
||||
getIntervals({
|
||||
baseInterval,
|
||||
baseSpread,
|
||||
intervalSpreadNormalized,
|
||||
intervalUnit,
|
||||
}),
|
||||
);
|
||||
}, [traceMetaData, globalTraceMetadata]);
|
||||
|
||||
return (
|
||||
<div ref={ref} style={{ flex: 1, overflow: 'inherit' }}>
|
||||
<svg
|
||||
style={{ overflow: 'inherit' }}
|
||||
viewBox={`0 0 ${width} ${Timeline_Height}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={styles['svg-container']}
|
||||
>
|
||||
<line
|
||||
x1={Timeline_H_Spacing}
|
||||
y1={Timeline_Height}
|
||||
x2={width - Timeline_H_Spacing}
|
||||
y2={Timeline_Height}
|
||||
stroke={isDarkMode ? 'white' : 'black'}
|
||||
strokeWidth="1"
|
||||
/>
|
||||
{intervals &&
|
||||
intervals.map((interval, index) => (
|
||||
<g
|
||||
transform={`translate(${
|
||||
Timeline_H_Spacing +
|
||||
(interval.percentage * (width - 2 * Timeline_H_Spacing)) / 100
|
||||
},0)`}
|
||||
className={styles['timeline-tick']}
|
||||
key={interval.label + interval.percentage + index}
|
||||
>
|
||||
<text y={13} fill={isDarkMode ? 'white' : 'black'}>
|
||||
{interval.label}
|
||||
</text>
|
||||
<line
|
||||
y1={Timeline_Height - 5}
|
||||
y2={Timeline_Height + 0.5}
|
||||
stroke={isDarkMode ? 'white' : 'black'}
|
||||
strokeWidth="1"
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timeline;
|
8
frontend/src/container/Timeline/style.module.css
Normal file
8
frontend/src/container/Timeline/style.module.css
Normal file
@ -0,0 +1,8 @@
|
||||
.svg-container {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
}
|
||||
.timeline-tick {
|
||||
text-anchor: middle;
|
||||
font-size: 0.6rem;
|
||||
}
|
4
frontend/src/container/Timeline/types.ts
Normal file
4
frontend/src/container/Timeline/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Interval {
|
||||
label: string;
|
||||
percentage: number;
|
||||
}
|
83
frontend/src/container/Timeline/utils.ts
Normal file
83
frontend/src/container/Timeline/utils.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
import {
|
||||
INTERVAL_UNITS,
|
||||
resolveTimeFromInterval,
|
||||
} from 'container/TraceDetail/utils';
|
||||
|
||||
export const getIntervalSpread = ({
|
||||
localTraceMetaData,
|
||||
globalTraceMetadata,
|
||||
}) => {
|
||||
const {
|
||||
globalStart: localStart,
|
||||
globalEnd: localEnd,
|
||||
spread: localSpread,
|
||||
} = localTraceMetaData;
|
||||
const { globalStart, globalEnd, globalSpread } = globalTraceMetadata;
|
||||
|
||||
let baseInterval = 0;
|
||||
|
||||
if (!isEqual(localTraceMetaData, globalTraceMetadata)) {
|
||||
baseInterval = localStart - globalStart;
|
||||
}
|
||||
|
||||
const MIN_INTERVALS = 5;
|
||||
const baseSpread = localSpread;
|
||||
let intervalSpread = (baseSpread / MIN_INTERVALS) * 1.0;
|
||||
const integerPartString = intervalSpread.toString().split('.')[0];
|
||||
const integerPartLength = integerPartString.length;
|
||||
const intervalSpreadNormalized =
|
||||
intervalSpread < 1.0
|
||||
? intervalSpread
|
||||
: Math.floor(
|
||||
Number(integerPartString) / Math.pow(10, integerPartLength - 1),
|
||||
) * Math.pow(10, integerPartLength - 1);
|
||||
return {
|
||||
baseInterval,
|
||||
baseSpread,
|
||||
intervalSpreadNormalized,
|
||||
};
|
||||
};
|
||||
|
||||
export const getIntervals = ({
|
||||
baseInterval,
|
||||
baseSpread,
|
||||
intervalSpreadNormalized,
|
||||
intervalUnit,
|
||||
}) => {
|
||||
const intervals: Interval[] = [
|
||||
{
|
||||
label: `${toFixed(resolveTimeFromInterval(baseInterval, intervalUnit), 2)}${
|
||||
intervalUnit.name
|
||||
}`,
|
||||
percentage: 0,
|
||||
},
|
||||
];
|
||||
|
||||
let tempBaseSpread = baseSpread;
|
||||
let elapsedIntervals = 0;
|
||||
|
||||
while (tempBaseSpread && intervals.length < 20) {
|
||||
let interval_time;
|
||||
if (tempBaseSpread <= 1.5 * intervalSpreadNormalized) {
|
||||
interval_time = elapsedIntervals + tempBaseSpread;
|
||||
tempBaseSpread = 0;
|
||||
} else {
|
||||
interval_time = elapsedIntervals + intervalSpreadNormalized;
|
||||
tempBaseSpread -= intervalSpreadNormalized;
|
||||
}
|
||||
elapsedIntervals = interval_time;
|
||||
|
||||
const interval: Interval = {
|
||||
label: `${toFixed(
|
||||
resolveTimeFromInterval(interval_time + baseInterval, intervalUnit),
|
||||
2,
|
||||
)}${intervalUnit.name}`,
|
||||
percentage: (interval_time / baseSpread) * 100,
|
||||
};
|
||||
intervals.push(interval);
|
||||
}
|
||||
|
||||
return intervals;
|
||||
};
|
@ -75,7 +75,7 @@ const SingleTags = (props: AllTagsProps): JSX.Element => {
|
||||
value={AllMenu.find((e) => e.key === selectedOperator)?.value || ''}
|
||||
>
|
||||
{AllMenu.map((e) => (
|
||||
<Option key={e.key} value={e.value}>
|
||||
<Option key={e.value} value={e.key}>
|
||||
{e.value}
|
||||
</Option>
|
||||
))}
|
||||
|
@ -13,14 +13,14 @@ export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
||||
const noOfTags = query.split(' AND ');
|
||||
|
||||
const tags: Tags = noOfTags.map((filter) => {
|
||||
const isInPresent = filter.includes('IN');
|
||||
const isNotInPresent = filter.includes('NOT_IN');
|
||||
const isInPresent = filter.includes('in');
|
||||
const isNotInPresent = filter.includes('not in');
|
||||
|
||||
if (!isNotInPresent || !isInPresent) {
|
||||
if (!isNotInPresent && !isInPresent) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
const splitBy = isNotInPresent ? 'NOT_IN' : isInPresent ? 'IN' : '';
|
||||
const splitBy = isNotInPresent ? 'not in' : isInPresent ? 'in' : '';
|
||||
|
||||
if (splitBy.length === 0) {
|
||||
isError = true;
|
||||
@ -40,13 +40,15 @@ export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
||||
|
||||
const removingFirstAndLastBrackets = `${filterForTags?.slice(1, -1)}`;
|
||||
|
||||
const noofFilters = removingFirstAndLastBrackets.split(',');
|
||||
const noofFilters = removingFirstAndLastBrackets
|
||||
.split(',')
|
||||
.map((e) => e.replaceAll(/"/g, ''));
|
||||
|
||||
noofFilters.forEach((e) => {
|
||||
const firstChar = e.charAt(0);
|
||||
const lastChar = e.charAt(e.length - 1);
|
||||
|
||||
if (!(firstChar === '"' && lastChar === '"')) {
|
||||
if (firstChar === '"' && lastChar === '"') {
|
||||
isError = true;
|
||||
}
|
||||
});
|
||||
@ -73,7 +75,9 @@ export const parseTagsToQuery = (tags: Tags): PayloadProps<string> => {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
return `${Key[0]} ${Operator} (${Values.map((e) => `"${e}"`).join(',')})`;
|
||||
return `${Key[0]} ${Operator} (${Values.map((e) => {
|
||||
return `"${e.replaceAll(/"/g, '')}"`;
|
||||
}).join(',')})`;
|
||||
})
|
||||
.join(' AND ');
|
||||
|
||||
|
@ -77,7 +77,7 @@ export const functions: Dropdown[] = [
|
||||
key: 'p50',
|
||||
},
|
||||
{
|
||||
displayValue: '90th percentile(duration in ns',
|
||||
displayValue: '90th percentile(duration in ns)',
|
||||
key: 'p90',
|
||||
},
|
||||
{
|
||||
|
@ -1,26 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import { TableProps, Tag } from 'antd';
|
||||
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
GetSpansAggregate,
|
||||
GetSpansAggregateProps,
|
||||
} from 'store/actions/trace/getInitialSpansAggregate';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import history from 'lib/history';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
dayjs.extend(duration);
|
||||
|
||||
const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => {
|
||||
const {
|
||||
spansAggregate,
|
||||
selectedFilter,
|
||||
@ -41,17 +39,16 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
title: 'Date',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
render: (value: TableType['timestamp']) => {
|
||||
sorter: true,
|
||||
render: (value: TableType['timestamp']): JSX.Element => {
|
||||
const day = dayjs(value);
|
||||
return <div>{day.format('DD/MM/YYYY hh:mm:ss A')}</div>;
|
||||
},
|
||||
sorter: (a, b) => dayjs(a.timestamp).diff(dayjs(b.timestamp)),
|
||||
},
|
||||
{
|
||||
title: 'Service',
|
||||
dataIndex: 'serviceName',
|
||||
key: 'serviceName',
|
||||
sorter: (a, b) => a.serviceName.length - b.serviceName.length,
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
@ -62,22 +59,19 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
title: 'Duration',
|
||||
dataIndex: 'durationNano',
|
||||
key: 'durationNano',
|
||||
sorter: (a, b) => a.durationNano - b.durationNano,
|
||||
render: (value: TableType['durationNano']) => {
|
||||
return (
|
||||
<div>
|
||||
{`${dayjs
|
||||
.duration({ milliseconds: value / 1000000 })
|
||||
.asMilliseconds()} ms`}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render: (value: TableType['durationNano']): JSX.Element => (
|
||||
<div>
|
||||
{`${dayjs
|
||||
.duration({ milliseconds: value / 1000000 })
|
||||
.asMilliseconds()} ms`}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Method',
|
||||
dataIndex: 'httpMethod',
|
||||
key: 'httpMethod',
|
||||
render: (value: TableType['httpMethod']) => {
|
||||
render: (value: TableType['httpMethod']): JSX.Element => {
|
||||
if (value.length === 0) {
|
||||
return <div>-</div>;
|
||||
}
|
||||
@ -88,8 +82,7 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
title: 'Status Code',
|
||||
dataIndex: 'httpCode',
|
||||
key: 'httpCode',
|
||||
sorter: (a, b) => a.httpCode.length - b.httpCode.length,
|
||||
render: (value: TableType['httpCode']) => {
|
||||
render: (value: TableType['httpCode']): JSX.Element => {
|
||||
if (value.length === 0) {
|
||||
return <div>-</div>;
|
||||
}
|
||||
@ -98,7 +91,13 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
},
|
||||
];
|
||||
|
||||
const onChangeHandler: TableProps<TableType>['onChange'] = (props) => {
|
||||
const onChangeHandler: TableProps<TableType>['onChange'] = (
|
||||
props,
|
||||
_,
|
||||
sort,
|
||||
) => {
|
||||
const { order = 'ascend' } = sort;
|
||||
|
||||
if (props.current && props.pageSize) {
|
||||
getSpansAggregate({
|
||||
maxTime: globalTime.maxTime,
|
||||
@ -107,6 +106,7 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
current: props.current,
|
||||
pageSize: props.pageSize,
|
||||
selectedTags,
|
||||
order: order === 'ascend' ? 'ascending' : 'descending',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -118,12 +118,10 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
loading={loading || filterLoading}
|
||||
columns={columns}
|
||||
onRow={(record) => ({
|
||||
onClick: () => {
|
||||
onClick: (): void => {
|
||||
history.push({
|
||||
pathname: ROUTES.TRACE + '/' + record.traceID,
|
||||
state: {
|
||||
spanId: record.spanID,
|
||||
},
|
||||
search: '?' + 'spanId=' + record.spanID,
|
||||
});
|
||||
},
|
||||
})}
|
||||
|
@ -0,0 +1,94 @@
|
||||
import { Button, Modal, Collapse } from 'antd';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
import { CustomSubText, CustomSubTitle } from './styles';
|
||||
// import Editor from 'components/Editor';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
|
||||
const ErrorTag = ({ event }: ErrorTagProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { isDarkMode } = useThemeMode();
|
||||
// const useTextRef = useRef('');
|
||||
|
||||
const [text, setText] = useState({
|
||||
text: '',
|
||||
subText: '',
|
||||
});
|
||||
|
||||
const onToggleHandler = (state: boolean) => {
|
||||
setIsOpen(state);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{event?.map(({ attributeMap, name }) => {
|
||||
const attributes = Object.keys(attributeMap);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
defaultActiveKey={[name || attributeMap.event]}
|
||||
expandIconPosition="right"
|
||||
>
|
||||
<Panel
|
||||
header={name || attributeMap?.event}
|
||||
key={name || attributeMap.event}
|
||||
>
|
||||
{attributes.map((event) => {
|
||||
const value = attributeMap[event];
|
||||
const isEllipsed = value.length > 24;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomSubTitle>{event}</CustomSubTitle>
|
||||
<CustomSubText ellipsis={isEllipsed} isDarkMode={isDarkMode}>
|
||||
{value}
|
||||
<br />
|
||||
{isEllipsed && (
|
||||
<Button
|
||||
style={{ padding: 0, margin: 0 }}
|
||||
onClick={() => {
|
||||
onToggleHandler(true);
|
||||
setText({
|
||||
subText: value,
|
||||
text: event,
|
||||
});
|
||||
// useTextRef.current = value;
|
||||
}}
|
||||
type="link"
|
||||
>
|
||||
View full log event message
|
||||
</Button>
|
||||
)}
|
||||
</CustomSubText>
|
||||
|
||||
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</Panel>
|
||||
</Collapse>
|
||||
);
|
||||
})}
|
||||
<Modal
|
||||
onCancel={() => onToggleHandler(false)}
|
||||
title="Log Message"
|
||||
visible={isOpen}
|
||||
destroyOnClose
|
||||
footer={[]}
|
||||
>
|
||||
<CustomSubTitle>{text.text}</CustomSubTitle>
|
||||
<CustomSubText ellipsis={false} isDarkMode={isDarkMode}>
|
||||
{text.subText}
|
||||
</CustomSubText>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface ErrorTagProps {
|
||||
event: ITraceTree['event'];
|
||||
}
|
||||
|
||||
export default ErrorTag;
|
@ -0,0 +1,75 @@
|
||||
import { Space, Tabs, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
import {
|
||||
CardContainer,
|
||||
CustomSubText,
|
||||
CustomSubTitle,
|
||||
CustomText,
|
||||
CustomTitle,
|
||||
} from './styles';
|
||||
import ErrorTag from './ErrorTag';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const SelectedSpanDetails = (props: SelectedSpanDetailsProps): JSX.Element => {
|
||||
const { tree } = props;
|
||||
const { isDarkMode } = useThemeMode();
|
||||
if (!tree) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const { name, tags, serviceName } = tree;
|
||||
|
||||
return (
|
||||
<CardContainer>
|
||||
<Space direction="vertical" style={{ marginLeft: '0.5rem' }}>
|
||||
<strong> Details for selected Span </strong>
|
||||
<Space direction="vertical" size={2}>
|
||||
<CustomTitle>Service</CustomTitle>
|
||||
<CustomText>{serviceName}</CustomText>
|
||||
</Space>
|
||||
<Space direction="vertical" size={2}>
|
||||
<CustomTitle>Operation</CustomTitle>
|
||||
<CustomText>{name}</CustomText>
|
||||
</Space>
|
||||
</Space>
|
||||
<Tabs defaultActiveKey="1" style={{ marginTop: '1rem' }}>
|
||||
<TabPane tab="Tags" key="1">
|
||||
{tags.length !== 0 ? (
|
||||
tags.map((tags) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{tags.value && (
|
||||
<>
|
||||
<CustomSubTitle>{tags.key}</CustomSubTitle>
|
||||
<CustomSubText isDarkMode={isDarkMode}>
|
||||
{tags.key === 'error' ? 'true' : tags.value}
|
||||
</CustomSubText>
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Typography>No tags in selected span</Typography>
|
||||
)}
|
||||
</TabPane>
|
||||
<TabPane tab="Events" key="2">
|
||||
{tree.event && Object.keys(tree.event).length !== 0 ? (
|
||||
<ErrorTag event={tree.event} />
|
||||
) : (
|
||||
<Typography>No events data in selected span</Typography>
|
||||
)}
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</CardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
interface SelectedSpanDetailsProps {
|
||||
tree?: ITraceTree;
|
||||
}
|
||||
|
||||
export default SelectedSpanDetails;
|
@ -0,0 +1,46 @@
|
||||
import { Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
const { Text, Title, Paragraph } = Typography;
|
||||
|
||||
export const CustomTitle = styled(Title)`
|
||||
&&& {
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CustomText = styled(Text)`
|
||||
&&& {
|
||||
color: #2d9cdb;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CustomSubTitle = styled(Title)`
|
||||
&&& {
|
||||
/* color: #bdbdbd; */
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface CustomSubTextProps {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const CustomSubText = styled(Paragraph)<CustomSubTextProps>`
|
||||
&&& {
|
||||
background: ${({ isDarkMode }) => (isDarkMode ? '#444' : '#ddd')};
|
||||
font-size: 12px;
|
||||
padding: 6px 8px;
|
||||
word-break: break-all;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardContainer = styled.div`
|
||||
margin: 0 0.5rem;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
`;
|
3
frontend/src/container/TraceDetail/TraceGraph.module.css
Normal file
3
frontend/src/container/TraceDetail/TraceGraph.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.trace-detail-content-spacing {
|
||||
margin-right: 1rem;
|
||||
}
|
200
frontend/src/container/TraceDetail/index.tsx
Normal file
200
frontend/src/container/TraceDetail/index.tsx
Normal file
@ -0,0 +1,200 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Col, Divider, Row, Typography, Space, Button } from 'antd';
|
||||
import { FilterOutlined } from '@ant-design/icons';
|
||||
import GanttChart from 'container/GantChart';
|
||||
import { getNodeById } from 'container/GantChart/utils';
|
||||
import Timeline from 'container/Timeline';
|
||||
import TraceFlameGraph from 'container/TraceFlameGraph';
|
||||
import dayjs from 'dayjs';
|
||||
import { spanServiceNameToColorMapping } from 'lib/getRandomColor';
|
||||
import { getSortedData } from './utils';
|
||||
import { ITraceTree, PayloadProps } from 'types/api/trace/getTraceItem';
|
||||
import { getSpanTreeMetadata } from 'utils/getSpanTreeMetadata';
|
||||
import { spanToTreeUtil } from 'utils/spanToTree';
|
||||
import SelectedSpanDetails from './SelectedSpanDetails';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import styles from './TraceGraph.module.css';
|
||||
import history from 'lib/history';
|
||||
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
|
||||
import { INTERVAL_UNITS } from './utils';
|
||||
|
||||
const TraceDetail = ({ response }: TraceDetailProps): JSX.Element => {
|
||||
const spanServiceColors = useMemo(
|
||||
() => spanServiceNameToColorMapping(response[0].events),
|
||||
[response],
|
||||
);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const [spanId, _setSpanId] = useState<string | null>(urlQuery.get('spanId'));
|
||||
|
||||
const [intervalUnit, setIntervalUnit] = useState(INTERVAL_UNITS[0]);
|
||||
const [searchSpanString, setSearchSpanString] = useState('');
|
||||
const [activeHoverId, setActiveHoverId] = useState<string>('');
|
||||
const [activeSelectedId, setActiveSelectedId] = useState<string>(spanId || '');
|
||||
|
||||
const [treeData, setTreeData] = useState<ITraceTree>(
|
||||
spanToTreeUtil(response[0].events),
|
||||
);
|
||||
|
||||
const { treeData: tree, ...traceMetaData } = useMemo(() => {
|
||||
return getSpanTreeMetadata(getSortedData(treeData), spanServiceColors);
|
||||
}, [treeData]);
|
||||
|
||||
const [globalTraceMetadata, _setGlobalTraceMetadata] = useState<object>({
|
||||
...traceMetaData,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (activeSelectedId) {
|
||||
history.replace({
|
||||
pathname: history.location.pathname,
|
||||
search: `?spanId=${activeSelectedId}`,
|
||||
});
|
||||
}
|
||||
}, [activeSelectedId]);
|
||||
|
||||
const getSelectedNode = useMemo(() => {
|
||||
return getNodeById(activeSelectedId, treeData);
|
||||
}, [activeSelectedId, treeData]);
|
||||
|
||||
const onSearchHandler = (value: string) => {
|
||||
setSearchSpanString(value);
|
||||
setTreeData(spanToTreeUtil(response[0].events));
|
||||
};
|
||||
const onFocusSelectedSpanHandler = () => {
|
||||
const treeNode = getNodeById(activeSelectedId, tree);
|
||||
if (treeNode) {
|
||||
setTreeData(treeNode);
|
||||
}
|
||||
};
|
||||
|
||||
const onResetHandler = () => {
|
||||
setTreeData(spanToTreeUtil(response[0].events));
|
||||
};
|
||||
|
||||
return (
|
||||
<Row style={{ flex: 1 }}>
|
||||
<Col flex={'auto'} style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Row className={styles['trace-detail-content-spacing']}>
|
||||
<Col
|
||||
flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}
|
||||
style={{ alignItems: 'center', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
Trace Details
|
||||
</Typography.Title>
|
||||
<Typography.Text style={{ margin: 0 }}>
|
||||
{traceMetaData.totalSpans} Span
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col flex={'auto'}>
|
||||
<TraceFlameGraph
|
||||
treeData={tree}
|
||||
traceMetaData={traceMetaData}
|
||||
hoveredSpanId={activeHoverId}
|
||||
selectedSpanId={activeSelectedId}
|
||||
onSpanHover={setActiveHoverId}
|
||||
onSpanSelect={setActiveSelectedId}
|
||||
intervalUnit={intervalUnit}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginTop: '2rem' }}>
|
||||
<Col
|
||||
flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{dayjs(traceMetaData.globalStart / 1e6).format('hh:mm:ssa MM/DD')}
|
||||
</Col>
|
||||
<Col
|
||||
flex="auto"
|
||||
style={{ overflow: 'visible' }}
|
||||
className={styles['trace-detail-content-spacing']}
|
||||
>
|
||||
<Timeline
|
||||
globalTraceMetadata={globalTraceMetadata}
|
||||
traceMetaData={traceMetaData}
|
||||
intervalUnit={intervalUnit}
|
||||
setIntervalUnit={setIntervalUnit}
|
||||
/>
|
||||
</Col>
|
||||
<Divider style={{ height: '100%', margin: '0' }} />
|
||||
</Row>
|
||||
<Row
|
||||
className={styles['trace-detail-content-spacing']}
|
||||
style={{ margin: '1.5rem 1rem 0.5rem' }}
|
||||
>
|
||||
<Col flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}>
|
||||
{/* <Search
|
||||
placeholder="Type to filter.."
|
||||
allowClear
|
||||
onSearch={onSearchHandler}
|
||||
style={{ width: 200 }}
|
||||
/> */}
|
||||
</Col>
|
||||
<Col flex={'auto'}>
|
||||
<Space
|
||||
style={{
|
||||
float: 'right',
|
||||
}}
|
||||
>
|
||||
<Button onClick={onFocusSelectedSpanHandler} icon={<FilterOutlined />}>
|
||||
Focus on selected span
|
||||
</Button>
|
||||
<Button type="default" onClick={onResetHandler}>
|
||||
Reset Focus
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
<div
|
||||
className={styles['trace-detail-content-spacing']}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
<GanttChart
|
||||
traceMetaData={traceMetaData}
|
||||
data={tree}
|
||||
activeSelectedId={activeSelectedId}
|
||||
activeHoverId={activeHoverId}
|
||||
setActiveHoverId={setActiveHoverId}
|
||||
setActiveSelectedId={setActiveSelectedId}
|
||||
spanId={spanId || ''}
|
||||
intervalUnit={intervalUnit}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col>
|
||||
<Divider style={{ height: '100%', margin: '0' }} type="vertical" />
|
||||
</Col>
|
||||
<Col
|
||||
md={5}
|
||||
sm={5}
|
||||
style={{
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<SelectedSpanDetails tree={getSelectedNode} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
interface TraceDetailProps {
|
||||
response: PayloadProps;
|
||||
}
|
||||
|
||||
export default TraceDetail;
|
57
frontend/src/container/TraceDetail/utils.ts
Normal file
57
frontend/src/container/TraceDetail/utils.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* string is present on the span or not
|
||||
*/
|
||||
import { ITraceTree, Span } from 'types/api/trace/getTraceItem';
|
||||
import { sortBy } from 'lodash-es';
|
||||
|
||||
export const filterSpansByString = (
|
||||
searchString: string,
|
||||
spans: Span[],
|
||||
): Span[] =>
|
||||
spans.filter((span) => {
|
||||
const spanWithoutChildren = [...span].slice(0, 11);
|
||||
return JSON.stringify(spanWithoutChildren).includes(searchString);
|
||||
});
|
||||
|
||||
export interface IIntervalUnit {
|
||||
name: 'ms' | 's' | 'm';
|
||||
multiplier: number;
|
||||
}
|
||||
export const INTERVAL_UNITS: IIntervalUnit[] = [
|
||||
{
|
||||
name: 'ms',
|
||||
multiplier: 1,
|
||||
},
|
||||
{
|
||||
name: 's',
|
||||
multiplier: 1 / 1e3,
|
||||
},
|
||||
{
|
||||
name: 'm',
|
||||
multiplier: 1 / (1e3 * 60),
|
||||
},
|
||||
];
|
||||
|
||||
export const resolveTimeFromInterval = (
|
||||
intervalTime: number,
|
||||
intervalUnit: IIntervalUnit,
|
||||
) => {
|
||||
return intervalTime * intervalUnit.multiplier;
|
||||
};
|
||||
|
||||
export const getSortedData = (treeData: ITraceTree) => {
|
||||
const traverse = (treeNode: ITraceTree, level: number = 0) => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
treeNode.children = sortBy(treeNode.children, (e) => e.startTime);
|
||||
|
||||
for (const childNode of treeNode.children) {
|
||||
traverse(childNode, level + 1);
|
||||
}
|
||||
};
|
||||
traverse(treeData, 1);
|
||||
|
||||
return treeData;
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
import { expect } from '@jest/globals';
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import TraceFlameGraph from 'container/TraceFlameGraph';
|
||||
|
||||
test('loads and displays greeting', async () => {
|
||||
const { asFragment } = render(<TraceFlameGraph />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`loads and displays greeting 1`] = `<DocumentFragment />`;
|
183
frontend/src/container/TraceFlameGraph/index.tsx
Normal file
183
frontend/src/container/TraceFlameGraph/index.tsx
Normal file
@ -0,0 +1,183 @@
|
||||
import React, { useState, useLayoutEffect, useMemo } from 'react';
|
||||
import Color from 'color';
|
||||
import { pushDStree } from 'store/actions';
|
||||
import {
|
||||
SpanItemContainer,
|
||||
TraceFlameGraphContainer,
|
||||
TOTAL_SPAN_HEIGHT,
|
||||
} from './styles';
|
||||
import {
|
||||
IIntervalUnit,
|
||||
resolveTimeFromInterval,
|
||||
} from 'container/TraceDetail/utils';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
import useThemeMode from 'hooks/useThemeMode';
|
||||
|
||||
const SpanItem = ({
|
||||
topOffset = 0, // top offset in px
|
||||
leftOffset = 0, // left offset in %
|
||||
width = 10, // width in %
|
||||
spanData,
|
||||
tooltipText,
|
||||
onSpanSelect, // function which gets invoked on clicking span
|
||||
onSpanHover,
|
||||
hoveredSpanId,
|
||||
selectedSpanId,
|
||||
}: {
|
||||
topOffset: number;
|
||||
leftOffset: number;
|
||||
width: number;
|
||||
spanData: pushDStree;
|
||||
tooltipText: string;
|
||||
onSpanSelect: Function;
|
||||
onSpanHover: Function;
|
||||
hoveredSpanId: string;
|
||||
selectedSpanId: string;
|
||||
}) => {
|
||||
const { serviceColour } = spanData;
|
||||
const [isSelected, setIsSelected] = useState<boolean>(false);
|
||||
const [isLocalHover, setIsLocalHover] = useState<boolean>(false);
|
||||
const { isDarkMode } = useThemeMode();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (
|
||||
!isSelected &&
|
||||
(spanData.id === hoveredSpanId || spanData.id === selectedSpanId)
|
||||
) {
|
||||
setIsSelected(true);
|
||||
}
|
||||
}, [hoveredSpanId, selectedSpanId]);
|
||||
|
||||
const handleHover = (hoverState: boolean) => {
|
||||
setIsLocalHover(hoverState);
|
||||
|
||||
if (hoverState) onSpanHover(spanData.id);
|
||||
else onSpanHover(null);
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
onSpanSelect(spanData.id);
|
||||
};
|
||||
|
||||
const spanColor = useMemo((): string => {
|
||||
const selectedSpanColor = isDarkMode
|
||||
? Color(serviceColour).lighten(0.3)
|
||||
: Color(serviceColour).darken(0.3);
|
||||
return `${isSelected ? selectedSpanColor : serviceColour}`;
|
||||
}, [isSelected, serviceColour]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SpanItemContainer
|
||||
title={tooltipText}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={() => {
|
||||
handleHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
handleHover(false);
|
||||
}}
|
||||
topOffset={topOffset}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
spanColor={spanColor}
|
||||
selected={isSelected}
|
||||
zIdx={isSelected ? 1 : 0}
|
||||
></SpanItemContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TraceFlameGraph = (props: {
|
||||
treeData: pushDStree;
|
||||
traceMetaData: any;
|
||||
onSpanHover: Function;
|
||||
onSpanSelect: Function;
|
||||
hoveredSpanId: string;
|
||||
selectedSpanId: string;
|
||||
intervalUnit: IIntervalUnit;
|
||||
}) => {
|
||||
if (!props.treeData || props.treeData.id === 'empty' || !props.traceMetaData) {
|
||||
return null;
|
||||
}
|
||||
const { intervalUnit } = props;
|
||||
|
||||
const {
|
||||
globalStart,
|
||||
globalEnd,
|
||||
spread,
|
||||
totalSpans,
|
||||
levels,
|
||||
} = props.traceMetaData;
|
||||
const RenderSpanRecursive = ({
|
||||
level = 0,
|
||||
spanData,
|
||||
parentLeftOffset = 0,
|
||||
onSpanHover,
|
||||
onSpanSelect,
|
||||
hoveredSpanId,
|
||||
selectedSpanId,
|
||||
}: {
|
||||
spanData: pushDStree;
|
||||
level?: number;
|
||||
parentLeftOffset?: number;
|
||||
onSpanHover: Function;
|
||||
onSpanSelect: Function;
|
||||
hoveredSpanId: string;
|
||||
selectedSpanId: string;
|
||||
}) => {
|
||||
if (!spanData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const leftOffset = ((spanData.startTime - globalStart) * 100) / spread;
|
||||
const width = ((spanData.value / 1e6) * 100) / spread;
|
||||
const toolTipText = `${spanData.name}\n${toFixed(
|
||||
resolveTimeFromInterval(spanData.value / 1e6, intervalUnit),
|
||||
2,
|
||||
)} ${intervalUnit.name}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SpanItem
|
||||
topOffset={level * TOTAL_SPAN_HEIGHT}
|
||||
leftOffset={leftOffset}
|
||||
width={width}
|
||||
spanData={spanData}
|
||||
tooltipText={toolTipText}
|
||||
onSpanHover={onSpanHover}
|
||||
onSpanSelect={onSpanSelect}
|
||||
hoveredSpanId={hoveredSpanId}
|
||||
selectedSpanId={selectedSpanId}
|
||||
/>
|
||||
{spanData.children.map((childData) => (
|
||||
<RenderSpanRecursive
|
||||
level={level + 1}
|
||||
spanData={childData}
|
||||
key={childData.id}
|
||||
parentLeftOffset={leftOffset + parentLeftOffset}
|
||||
onSpanHover={onSpanHover}
|
||||
onSpanSelect={onSpanSelect}
|
||||
hoveredSpanId={hoveredSpanId}
|
||||
selectedSpanId={selectedSpanId}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TraceFlameGraphContainer height={TOTAL_SPAN_HEIGHT * levels}>
|
||||
<RenderSpanRecursive
|
||||
spanData={props.treeData}
|
||||
onSpanHover={props.onSpanHover}
|
||||
onSpanSelect={props.onSpanSelect}
|
||||
hoveredSpanId={props.hoveredSpanId}
|
||||
selectedSpanId={props.selectedSpanId}
|
||||
/>
|
||||
</TraceFlameGraphContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TraceFlameGraph;
|
38
frontend/src/container/TraceFlameGraph/styles.ts
Normal file
38
frontend/src/container/TraceFlameGraph/styles.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const SPAN_HEIGHT = 10;
|
||||
const SPAN_V_PADDING = 1;
|
||||
export const TOTAL_SPAN_HEIGHT = SPAN_HEIGHT + 2 * SPAN_V_PADDING;
|
||||
|
||||
/**
|
||||
* An individual span for traces flame graph
|
||||
*/
|
||||
export const SpanItemContainer = styled.div<{
|
||||
topOffset: number;
|
||||
leftOffset: number;
|
||||
width: number;
|
||||
spanColor: string;
|
||||
selected: boolean;
|
||||
zIdx: number;
|
||||
}>`
|
||||
position: absolute;
|
||||
top: ${(props) => props.topOffset}px;
|
||||
left: ${(props) => props.leftOffset}%;
|
||||
width: ${(props) => props.width}%;
|
||||
height: ${SPAN_HEIGHT}px;
|
||||
margin: ${SPAN_V_PADDING}px 0;
|
||||
background-color: ${({ spanColor }) => spanColor};
|
||||
border-radius: ${SPAN_HEIGHT / 2}px;
|
||||
z-index: ${(props) => props.zIdx};
|
||||
`;
|
||||
|
||||
/**
|
||||
* Container for spans, for traces flame graph.
|
||||
*/
|
||||
export const TraceFlameGraphContainer = styled.div<{
|
||||
height: number;
|
||||
}>`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: ${({ height }) => (height ? height : 120)}px;
|
||||
`;
|
15
frontend/src/hooks/useThemeMode.ts
Normal file
15
frontend/src/hooks/useThemeMode.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
export interface IUseThemeModeReturn {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
const useThemeMode = () => {
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
return { isDarkMode };
|
||||
};
|
||||
|
||||
export default useThemeMode;
|
10
frontend/src/hooks/useUrlQuery.ts
Normal file
10
frontend/src/hooks/useUrlQuery.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
function useUrlQuery() {
|
||||
const { search } = useLocation();
|
||||
|
||||
return useMemo(() => new URLSearchParams(search), [search]);
|
||||
}
|
||||
|
||||
export default useUrlQuery;
|
67
frontend/src/lib/__tests__/getStep.test.ts
Normal file
67
frontend/src/lib/__tests__/getStep.test.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import dayjs from 'dayjs';
|
||||
import getStep, { DefaultStepSize } from 'lib/getStep';
|
||||
|
||||
describe('lib/getStep', () => {
|
||||
test('should return default step when the given range is less than 1 day', () => {
|
||||
const start = dayjs();
|
||||
const end = start.add(1, 'hour');
|
||||
const startUnix = start.valueOf();
|
||||
const endUnix = end.valueOf();
|
||||
|
||||
expect(
|
||||
getStep({
|
||||
start: startUnix / 1e3,
|
||||
end: endUnix / 1e3,
|
||||
inputFormat: 's',
|
||||
}),
|
||||
).toEqual(DefaultStepSize);
|
||||
|
||||
expect(
|
||||
getStep({
|
||||
start: startUnix,
|
||||
end: endUnix,
|
||||
inputFormat: 'ms',
|
||||
}),
|
||||
).toEqual(DefaultStepSize);
|
||||
|
||||
expect(
|
||||
getStep({
|
||||
start: startUnix * 1e6,
|
||||
end: endUnix * 1e6,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
).toEqual(DefaultStepSize);
|
||||
});
|
||||
|
||||
test('should return relevant step when the given range is greater than 1 day', () => {
|
||||
const start = dayjs();
|
||||
const end = start.add(1, 'Day').add(1, 'Second');
|
||||
const startUnix = start.valueOf();
|
||||
const endUnix = end.valueOf();
|
||||
|
||||
const expectedStepSize = end.diff(start, 'days') * DefaultStepSize;
|
||||
expect(
|
||||
getStep({
|
||||
start: startUnix / 1e3,
|
||||
end: endUnix / 1e3,
|
||||
inputFormat: 's',
|
||||
}),
|
||||
).toEqual(expectedStepSize);
|
||||
|
||||
expect(
|
||||
getStep({
|
||||
start: startUnix,
|
||||
end: endUnix,
|
||||
inputFormat: 'ms',
|
||||
}),
|
||||
).toEqual(expectedStepSize);
|
||||
|
||||
expect(
|
||||
getStep({
|
||||
start: startUnix * 1e6,
|
||||
end: endUnix * 1e6,
|
||||
inputFormat: 'ns',
|
||||
}),
|
||||
).toEqual(expectedStepSize);
|
||||
});
|
||||
});
|
@ -1,14 +1,17 @@
|
||||
import { span } from 'store/actions';
|
||||
|
||||
export const colors = [
|
||||
'#2F80ED',
|
||||
'#BB6BD9',
|
||||
'#F2994A',
|
||||
'#219653',
|
||||
'#56CCF2',
|
||||
'#F2C94C',
|
||||
'#219653',
|
||||
'#2F80ED',
|
||||
'#EB5757',
|
||||
'#BB6BD9',
|
||||
'#BDBDBD',
|
||||
];
|
||||
|
||||
export const errorColor = '#d32f2f';
|
||||
|
||||
export function getRandomNumber(min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
@ -18,4 +21,16 @@ const getRandomColor = (): string => {
|
||||
return colors[index];
|
||||
};
|
||||
|
||||
export const spanServiceNameToColorMapping = (spans: span[]) => {
|
||||
const serviceNameSet = new Set();
|
||||
spans.forEach((spanItem) => {
|
||||
serviceNameSet.add(spanItem[3]);
|
||||
});
|
||||
const serviceToColorMap: { [key: string]: string } = {};
|
||||
Array.from(serviceNameSet).forEach((serviceName, idx) => {
|
||||
serviceToColorMap[`${serviceName}`] = colors[idx % colors.length];
|
||||
});
|
||||
return serviceToColorMap;
|
||||
};
|
||||
|
||||
export default getRandomColor;
|
||||
|
43
frontend/src/lib/getStep.ts
Normal file
43
frontend/src/lib/getStep.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
type DateType = number | string;
|
||||
type DateInputFormatType = 's' | 'ms' | 'ns';
|
||||
|
||||
interface GetStepInput {
|
||||
start: DateType;
|
||||
end: DateType;
|
||||
inputFormat: DateInputFormatType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts given timestamp to ms.
|
||||
*/
|
||||
const convertToMs = (timestamp: number, inputFormat: DateInputFormatType) => {
|
||||
switch (inputFormat) {
|
||||
case 's':
|
||||
return timestamp * 1e3;
|
||||
case 'ms':
|
||||
return timestamp * 1;
|
||||
case 'ns':
|
||||
return timestamp / 1e6;
|
||||
}
|
||||
};
|
||||
|
||||
export const DefaultStepSize = 60;
|
||||
|
||||
/**
|
||||
* Returns relevant step size based on given start and end date.
|
||||
*/
|
||||
const getStep = ({ start, end, inputFormat = 'ms' }: GetStepInput): number => {
|
||||
const startDate = dayjs(convertToMs(Number(start), inputFormat));
|
||||
const endDate = dayjs(convertToMs(Number(end), inputFormat));
|
||||
const diffDays = Math.abs(endDate.diff(startDate, 'days'));
|
||||
|
||||
if (diffDays > 1) {
|
||||
return DefaultStepSize * diffDays;
|
||||
}
|
||||
|
||||
return DefaultStepSize;
|
||||
};
|
||||
|
||||
export default getStep;
|
@ -1,129 +0,0 @@
|
||||
import { Card, Tag as AntTag } from 'antd';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { TagItem, TraceFilters, updateTraceFilters } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Tag = styled(AntTag)`
|
||||
.anticon {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface FilterStateDisplayProps {
|
||||
traceFilters: TraceFilters;
|
||||
updateTraceFilters: (props: TraceFilters) => void;
|
||||
}
|
||||
|
||||
const _FilterStateDisplay = (props: FilterStateDisplayProps): JSX.Element => {
|
||||
const { traceFilters, updateTraceFilters } = props;
|
||||
|
||||
function handleCloseTag(value: string): void {
|
||||
if (value === 'service') {
|
||||
updateTraceFilters({ ...traceFilters, service: '' });
|
||||
}
|
||||
if (value === 'operation') {
|
||||
updateTraceFilters({ ...traceFilters, operation: '' });
|
||||
}
|
||||
if (value === 'maxLatency') {
|
||||
updateTraceFilters({
|
||||
...traceFilters,
|
||||
latency: { max: '', min: traceFilters.latency?.min || '' },
|
||||
});
|
||||
}
|
||||
if (value === 'minLatency') {
|
||||
updateTraceFilters({
|
||||
...traceFilters,
|
||||
latency: { min: '', max: traceFilters.latency?.max || '' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleCloseTagElement(item: TagItem): void {
|
||||
props.updateTraceFilters({
|
||||
...props.traceFilters,
|
||||
tags: props.traceFilters.tags?.filter((elem) => elem !== item),
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
style={{ padding: 6, marginTop: 10, marginBottom: 10 }}
|
||||
bodyStyle={{ padding: 6 }}
|
||||
>
|
||||
{props.traceFilters.service === '' ||
|
||||
props.traceFilters.operation === undefined ? null : (
|
||||
<Tag
|
||||
style={{ fontSize: 14, padding: 8 }}
|
||||
closable
|
||||
onClose={(): void => {
|
||||
handleCloseTag('service');
|
||||
}}
|
||||
>
|
||||
service:{props.traceFilters.service}
|
||||
</Tag>
|
||||
)}
|
||||
{props.traceFilters.operation === '' ||
|
||||
props.traceFilters.operation === undefined ? null : (
|
||||
<Tag
|
||||
style={{ fontSize: 14, padding: 8 }}
|
||||
closable
|
||||
onClose={(): void => {
|
||||
handleCloseTag('operation');
|
||||
}}
|
||||
>
|
||||
operation:{props.traceFilters.operation}
|
||||
</Tag>
|
||||
)}
|
||||
{props.traceFilters.latency === undefined ||
|
||||
props.traceFilters.latency?.min === '' ? null : (
|
||||
<Tag
|
||||
style={{ fontSize: 14, padding: 8 }}
|
||||
closable
|
||||
onClose={(): void => {
|
||||
handleCloseTag('minLatency');
|
||||
}}
|
||||
>
|
||||
minLatency:
|
||||
{(parseInt(traceFilters?.latency?.min || '0') / 1000000).toString()}ms
|
||||
</Tag>
|
||||
)}
|
||||
{props.traceFilters.latency === undefined ||
|
||||
props.traceFilters.latency?.max === '' ? null : (
|
||||
<Tag
|
||||
style={{ fontSize: 14, padding: 8 }}
|
||||
closable
|
||||
onClose={(): void => {
|
||||
handleCloseTag('maxLatency');
|
||||
}}
|
||||
>
|
||||
maxLatency:
|
||||
{(parseInt(traceFilters?.latency?.max || '0') / 1000000).toString()}ms
|
||||
</Tag>
|
||||
)}
|
||||
{props.traceFilters.tags === undefined
|
||||
? null
|
||||
: props.traceFilters.tags.map((item) => (
|
||||
<Tag
|
||||
style={{ fontSize: 14, padding: 8 }}
|
||||
closable
|
||||
key={`${item.key}-${item.operator}-${item.value}`}
|
||||
onClose={(): void => {
|
||||
handleCloseTagElement(item);
|
||||
}}
|
||||
>
|
||||
{item.key} {item.operator} {item.value}
|
||||
</Tag>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState): { traceFilters: TraceFilters } => {
|
||||
return { traceFilters: state.traceFilters };
|
||||
};
|
||||
|
||||
export const FilterStateDisplay = connect(mapStateToProps, {
|
||||
updateTraceFilters: updateTraceFilters,
|
||||
})(_FilterStateDisplay);
|
@ -1,109 +0,0 @@
|
||||
import { Card, Space, Tabs, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { pushDStree } from 'store/actions';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const { Text, Title, Paragraph } = Typography;
|
||||
|
||||
interface SelectedSpanDetailsProps {
|
||||
data: pushDStree;
|
||||
}
|
||||
|
||||
// Check this discussion for antd with styled components
|
||||
// https://gist.github.com/newswim/fa916c66477ddd5952f7d6548e6a0605
|
||||
|
||||
const CustomTitle = styled(Title)`
|
||||
&&& {
|
||||
color: #f2f2f2;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CustomText = styled(Text)`
|
||||
&&& {
|
||||
color: #2d9cdb;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CustomSubTitle = styled(Title)`
|
||||
&&& {
|
||||
color: #bdbdbd;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CustomSubText = styled(Paragraph)`
|
||||
&&& {
|
||||
background: #4f4f4f;
|
||||
color: #2d9cdb;
|
||||
font-size: 12px;
|
||||
padding: 6px 8px;
|
||||
word-break: break-all;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CardContainer = styled(Card)`
|
||||
.ant-card-body {
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
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];
|
||||
|
||||
return (
|
||||
<CardContainer>
|
||||
<Space direction="vertical">
|
||||
<strong> Details for selected Span </strong>
|
||||
<Space direction="vertical" size={2}>
|
||||
<CustomTitle style={{ marginTop: '18px' }}>Service</CustomTitle>
|
||||
<CustomText>{service}</CustomText>
|
||||
</Space>
|
||||
<Space direction="vertical" size={2}>
|
||||
<CustomTitle>Operation</CustomTitle>
|
||||
<CustomText>{operation}</CustomText>
|
||||
</Space>
|
||||
</Space>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane tab="Tags" key="1">
|
||||
{spanTags &&
|
||||
spanTags.map((tags) => {
|
||||
return (
|
||||
<>
|
||||
{tags.value && (
|
||||
<>
|
||||
<CustomSubTitle>{tags.key}</CustomSubTitle>
|
||||
<CustomSubText>
|
||||
{tags.key === 'error' ? 'true' : tags.value}
|
||||
</CustomSubText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</TabPane>
|
||||
<TabPane tab="Errors" key="2">
|
||||
{spanTags &&
|
||||
spanTags
|
||||
.filter((tags) => tags.key === 'error')
|
||||
.map((error) => (
|
||||
<>
|
||||
<CustomSubTitle>{error.key}</CustomSubTitle>
|
||||
<CustomSubText>true</CustomSubText>
|
||||
</>
|
||||
))}
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</CardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectedSpanDetails;
|
@ -1,15 +0,0 @@
|
||||
.row-styles {
|
||||
cursor: pointer;
|
||||
}
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .ant-tabs-nav-list {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
} */
|
||||
|
||||
.ant-table-body table {
|
||||
margin-bottom: 64px;
|
||||
}
|
@ -1,389 +0,0 @@
|
||||
import './TraceGanttChart.css';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Progress,
|
||||
Row,
|
||||
Table as TableComponent,
|
||||
Tabs,
|
||||
} from 'antd';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { has, isEmpty, max } from 'lodash-es';
|
||||
import traverseTreeData from 'modules/Traces/TraceGanttChart/TraceGanttChartHelpers';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { pushDStree } from 'store/actions';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
color: #f2f2f2;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-right: 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
`;
|
||||
|
||||
const Table = styled(TableComponent)`
|
||||
.ant-tabs-nav-list {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
|
||||
interface TraceGanttChartProps {
|
||||
treeData: pushDStree[];
|
||||
clickedSpan: pushDStree;
|
||||
selectedSpan: pushDStree;
|
||||
resetZoom: (value: boolean) => {};
|
||||
setSpanTagsInfo: (p: { data: any }) => {};
|
||||
}
|
||||
|
||||
const TraceGanttChart = ({
|
||||
treeData,
|
||||
clickedSpan,
|
||||
selectedSpan,
|
||||
resetZoom,
|
||||
setSpanTagsInfo,
|
||||
}: TraceGanttChartProps) => {
|
||||
const checkStrictly = true;
|
||||
const [selectedRows, setSelectedRows] = useState<string[]>([]);
|
||||
const [clickedSpanData, setClickedSpanData] = useState(clickedSpan);
|
||||
const [defaultExpandedRows, setDefaultExpandedRows] = useState<string[]>([]);
|
||||
const [sortedTreeData, setSortedTreeData] = useState(treeData);
|
||||
const [isReset, setIsReset] = useState(false);
|
||||
const [tabsContainerWidth, setTabsContainerWidth] = useState(0);
|
||||
const tableRef = useRef('');
|
||||
const tabsContainer = document.querySelector<HTMLElement>(
|
||||
'#collapsable .ant-tabs-nav-list',
|
||||
);
|
||||
|
||||
const tabs = document.querySelectorAll('#collapsable .ant-tabs-tab');
|
||||
|
||||
const { id } = treeData || 'id';
|
||||
let maxGlobal = 0;
|
||||
let minGlobal = 0;
|
||||
let medianGlobal = 0;
|
||||
const endTimeArray: [] = [];
|
||||
|
||||
useEffect(() => {
|
||||
if (id !== 'empty') {
|
||||
setSortedTreeData(treeData);
|
||||
if (clickedSpan) {
|
||||
setClickedSpanData(clickedSpan);
|
||||
}
|
||||
if (tabsContainer) {
|
||||
setTabsContainerWidth(tabsContainer.offsetWidth);
|
||||
}
|
||||
}
|
||||
handleScroll(selectedSpan?.id);
|
||||
}, [sortedTreeData, treeData, clickedSpan]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isEmpty(clickedSpanData) &&
|
||||
clickedSpan &&
|
||||
!selectedRows.includes(clickedSpan.id) &&
|
||||
!isReset
|
||||
) {
|
||||
setSelectedRows([clickedSpan.id]);
|
||||
getParentKeys(clickedSpan);
|
||||
handleFocusOnSelectedPath('', [clickedSpan.id]);
|
||||
}
|
||||
}, [clickedSpan, selectedRows, isReset, clickedSpanData]);
|
||||
|
||||
const parentKeys: string[] = [];
|
||||
const childrenKeys: string[] = [];
|
||||
|
||||
const getParentKeys = (obj) => {
|
||||
if (has(obj, 'parent')) {
|
||||
parentKeys.push(obj.parent.id);
|
||||
getParentKeys(obj.parent);
|
||||
}
|
||||
};
|
||||
|
||||
const getChildrenKeys = (obj: pushDStree) => {
|
||||
if (has(obj, 'children')) {
|
||||
childrenKeys.push(obj.id);
|
||||
if (!isEmpty(obj.children)) {
|
||||
obj.children.map((item) => {
|
||||
getChildrenKeys(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(selectedSpan) && isEmpty(clickedSpan)) {
|
||||
getParentKeys(selectedSpan);
|
||||
const keys = [selectedSpan?.id, ...parentKeys];
|
||||
setDefaultExpandedRows(keys);
|
||||
setSelectedRows([selectedSpan.id, clickedSpan]);
|
||||
// setSpanTagsInfo({data: selectedSpan})
|
||||
} else {
|
||||
setSelectedRows([treeData?.[0]?.id]);
|
||||
setDefaultExpandedRows([treeData?.[0]?.id]);
|
||||
// /.setSpanTagsInfo({data: treeData?.[0]})
|
||||
}
|
||||
}, [selectedSpan, treeData, clickedSpan]);
|
||||
|
||||
const getMaxEndTime = (treeData) => {
|
||||
if (treeData.length > 0) {
|
||||
if (treeData?.id !== 'empty') {
|
||||
return Array.from(treeData).map((item, key) => {
|
||||
if (!isEmpty(item.children)) {
|
||||
endTimeArray.push(item.time / 1000000 + item.startTime);
|
||||
getMaxEndTime(item.children);
|
||||
} else {
|
||||
endTimeArray.push(item.time / 1000000 + item.startTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (id !== 'empty') {
|
||||
getMaxEndTime(treeData);
|
||||
maxGlobal = max(endTimeArray);
|
||||
minGlobal = treeData?.[0]?.startTime;
|
||||
medianGlobal = (minGlobal + maxGlobal) / 2;
|
||||
}
|
||||
|
||||
/*
|
||||
timeDiff = maxGlobal - startTime
|
||||
totalTime = maxGlobal - minGlobal
|
||||
totalWidth = width of container
|
||||
*/
|
||||
const getPaddingLeft = (timeDiff, totalTime, totalWidth) => {
|
||||
return ((timeDiff / totalTime) * totalWidth).toFixed(0);
|
||||
};
|
||||
|
||||
const tabMinVal = 0;
|
||||
const tabMedianVal = (medianGlobal - minGlobal).toFixed(0);
|
||||
const tabMaxVal = (maxGlobal - minGlobal).toFixed(0);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<Tabs>
|
||||
<TabPane tab={tabMinVal + 'ms'} key="1" />
|
||||
<TabPane tab={tabMedianVal + 'ms'} key="2" />
|
||||
<TabPane tab={tabMaxVal + 'ms'} key="3" />
|
||||
</Tabs>
|
||||
),
|
||||
dataIndex: 'trace',
|
||||
name: 'trace',
|
||||
render: (_, record: pushDStree) => {
|
||||
const widths = [];
|
||||
let length;
|
||||
|
||||
if (widths.length < tabs.length) {
|
||||
Array.from(tabs).map((tab) => {
|
||||
widths.push(tab.offsetWidth);
|
||||
});
|
||||
}
|
||||
|
||||
let paddingLeft = 0;
|
||||
const startTime = parseFloat(record.startTime.toString());
|
||||
const duration = parseFloat((record.time / 1000000).toFixed(2));
|
||||
paddingLeft = parseInt(
|
||||
getPaddingLeft(
|
||||
startTime - minGlobal,
|
||||
maxGlobal - minGlobal,
|
||||
tabsContainerWidth,
|
||||
),
|
||||
);
|
||||
let textPadding = paddingLeft;
|
||||
if (paddingLeft === tabsContainerWidth - 20) {
|
||||
textPadding = tabsContainerWidth - 40;
|
||||
}
|
||||
length = ((duration / (maxGlobal - startTime)) * 100).toFixed(2);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ paddingLeft: textPadding + 'px' }}>{duration}ms</div>
|
||||
<Progress
|
||||
percent={length}
|
||||
showInfo={false}
|
||||
style={{ paddingLeft: paddingLeft + 'px' }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleFocusOnSelectedPath = (event, selectedRowsList = selectedRows) => {
|
||||
if (!isEmpty(selectedRowsList)) {
|
||||
// initializing the node
|
||||
let node: pushDStree = {
|
||||
children: [],
|
||||
id: '',
|
||||
name: '',
|
||||
startTime: 0,
|
||||
tags: [],
|
||||
time: 0,
|
||||
value: 0,
|
||||
};
|
||||
|
||||
traverseTreeData(treeData, (item: pushDStree) => {
|
||||
if (item.id === selectedRowsList[0]) {
|
||||
node = item;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
setSpanTagsInfo({ data: node });
|
||||
} catch (e) {
|
||||
// TODO: error logging.
|
||||
console.error('Node not found in Tree Data.');
|
||||
}
|
||||
|
||||
// get the parent of the node
|
||||
getParentKeys(node);
|
||||
|
||||
// get the children of the node
|
||||
getChildrenKeys(node);
|
||||
|
||||
const rows = document.querySelectorAll('#collapsable table tbody tr');
|
||||
rows.forEach((row) => {
|
||||
const attribKey = row.getAttribute('data-row-key') || '';
|
||||
if (
|
||||
!isEmpty(attribKey) &&
|
||||
!selectedRowsList.includes(attribKey) &&
|
||||
!childrenKeys.includes(attribKey)
|
||||
) {
|
||||
row.classList.add('hide');
|
||||
}
|
||||
});
|
||||
setDefaultExpandedRows([...parentKeys, ...childrenKeys]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetFocus = () => {
|
||||
const rows = document.querySelectorAll('#collapsable table tbody tr');
|
||||
rows.forEach((row) => {
|
||||
row.classList.remove('hide');
|
||||
});
|
||||
|
||||
resetZoom(true);
|
||||
};
|
||||
|
||||
const handleScroll = (id: string): void => {
|
||||
if (!isEmpty(id)) {
|
||||
const selectedRow = document.querySelectorAll<HTMLElement>(
|
||||
`[data-row-key='${id}']`,
|
||||
);
|
||||
selectedRow?.[0]?.scrollIntoView();
|
||||
}
|
||||
};
|
||||
|
||||
const rowSelection = {
|
||||
onChange: (selectedRowKeys: []) => {
|
||||
setSelectedRows(selectedRowKeys);
|
||||
setClickedSpanData({});
|
||||
if (isEmpty(selectedRowKeys)) {
|
||||
setIsReset(true);
|
||||
} else {
|
||||
setIsReset(false);
|
||||
}
|
||||
},
|
||||
onSelect: (record: pushDStree) => {
|
||||
handleRowOnClick(record);
|
||||
},
|
||||
selectedRowKeys: selectedRows,
|
||||
};
|
||||
|
||||
const handleRowOnClick = (record: pushDStree) => {
|
||||
let node = {};
|
||||
traverseTreeData(treeData, (item: pushDStree) => {
|
||||
if (item.id === record.id) {
|
||||
node = item;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
setSpanTagsInfo({ data: node });
|
||||
} catch (e) {
|
||||
// TODO: error logging.
|
||||
console.error('Node not found in TreeData.');
|
||||
}
|
||||
|
||||
const selectedRowKeys = selectedRows;
|
||||
if (selectedRowKeys.indexOf(record.id) >= 0) {
|
||||
selectedRowKeys.splice(selectedRowKeys.indexOf(record.key), 1);
|
||||
} else {
|
||||
selectedRowKeys.push(record.id);
|
||||
}
|
||||
setSelectedRows([record.id]);
|
||||
};
|
||||
|
||||
const handleOnExpandedRowsChange = (item: string[]) => {
|
||||
setDefaultExpandedRows(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{id !== 'empty' && (
|
||||
<>
|
||||
<Row
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
gutter={32}
|
||||
style={{
|
||||
marginBottom: '24px',
|
||||
}}
|
||||
>
|
||||
<Col>
|
||||
<TextToolTip
|
||||
text={`More details on how to understand trace details`}
|
||||
url="https://signoz.io/docs/userguide/trace-details/"
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<StyledButton onClick={handleFocusOnSelectedPath}>
|
||||
Focus on selected path
|
||||
</StyledButton>
|
||||
<StyledButton onClick={handleResetFocus}> Reset Focus </StyledButton>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Table
|
||||
refs={tableRef}
|
||||
hideSelectAll={true}
|
||||
columns={columns}
|
||||
rowSelection={{ ...rowSelection, checkStrictly, type: 'radio' }}
|
||||
dataSource={sortedTreeData}
|
||||
rowKey="id"
|
||||
sticky={true}
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: () => handleRowOnClick(record), // click row
|
||||
};
|
||||
}}
|
||||
onExpandedRowsChange={(keys) =>
|
||||
handleOnExpandedRowsChange(keys.map((e) => e.toString()))
|
||||
}
|
||||
pagination={false}
|
||||
expandable={{
|
||||
expandedRowKeys: defaultExpandedRows,
|
||||
}}
|
||||
scroll={{ y: 540 }}
|
||||
rowClassName="row-styles"
|
||||
filterMultiple={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TraceGanttChart;
|
@ -1,40 +0,0 @@
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { pushDStree } from 'store/actions';
|
||||
|
||||
interface itemProps {
|
||||
treeData: pushDStree[];
|
||||
marked: boolean;
|
||||
}
|
||||
|
||||
// Doing DFS traversal on the tree.
|
||||
// Callback to be called for each element in the tree once.
|
||||
const traverseTreeData = (
|
||||
tree: pushDStree[],
|
||||
callback: (item: pushDStree) => void,
|
||||
): void => {
|
||||
if (isEmpty(tree) || tree[0].id === 'empty') return;
|
||||
const node = { treeData: tree, marked: false };
|
||||
const stk: [itemProps] = [node];
|
||||
|
||||
while (!isEmpty(stk)) {
|
||||
const x = stk[stk.length - 1];
|
||||
|
||||
// marked means seeing the node for the second time.
|
||||
if (x.marked) {
|
||||
x.marked = false;
|
||||
stk.pop();
|
||||
x.treeData.map((item: pushDStree) => {
|
||||
callback(item);
|
||||
});
|
||||
} else {
|
||||
x.marked = true;
|
||||
x.treeData.map((item) => {
|
||||
if (item.children.length > 0) {
|
||||
stk.push({ treeData: item.children, marked: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default traverseTreeData;
|
@ -1 +0,0 @@
|
||||
export { default } from './TraceGanttChart';
|
@ -1,59 +0,0 @@
|
||||
.d3-tip {
|
||||
line-height: 1;
|
||||
padding: 2px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Creates a small triangle extender for the tooltip */
|
||||
.d3-tip:after {
|
||||
box-sizing: border-box;
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
width: 100%;
|
||||
line-height: 1;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
content: "\25BC";
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Style northward tooltips differently */
|
||||
.d3-tip.n:after {
|
||||
margin: -1px 0 0 0;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* SVG element */
|
||||
/* Way to add borders in SVG - https://stackoverflow.com/questions/18330344/how-to-add-border-outline-stroke-to-svg-elements-in-css */
|
||||
.frame {
|
||||
fill: none;
|
||||
stroke: rgba(255, 255, 255, 0.25);
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* Prevent text vertical shift on hover */
|
||||
.d3-flame-graph-label {
|
||||
border: 1px dotted transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Transparency simulates sub pixel border https://stackoverflow.com/questions/13891177/css-border-less-than-1px */
|
||||
|
||||
.d3-flame-graph-label:hover {
|
||||
border-color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
/*
|
||||
.d3-flame-graph-label:hover {
|
||||
border: 1px solid;
|
||||
border-color: rgba(255, 255, 255, 0.75);
|
||||
} */
|
||||
|
||||
.fade:not(.show) {
|
||||
opacity: 0.5;
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
import './TraceGraph.css';
|
||||
|
||||
import { Affix, Card, Col, Row, Space } from 'antd';
|
||||
import * as d3 from 'd3';
|
||||
import { flamegraph } from 'd3-flame-graph';
|
||||
import * as d3Tip from 'd3-tip';
|
||||
import { isEmpty, sortBy } from 'lodash-es';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import {
|
||||
fetchTraceItem,
|
||||
pushDStree,
|
||||
spansWSameTraceIDResponse,
|
||||
} from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import styled from 'styled-components';
|
||||
import { spanToTreeUtil } from 'utils/spanToTree';
|
||||
|
||||
import SelectedSpanDetails from './SelectedSpanDetails';
|
||||
import TraceGanttChart from './TraceGanttChart';
|
||||
|
||||
interface TraceGraphProps {
|
||||
traceItem: spansWSameTraceIDResponse;
|
||||
fetchTraceItem: Function;
|
||||
}
|
||||
|
||||
const TraceGanttChartContainer = styled(Card)`
|
||||
background: #333333;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
|
||||
const _TraceGraph = (props: TraceGraphProps) => {
|
||||
const location = useLocation();
|
||||
const spanId = location?.state?.spanId;
|
||||
const { id } = useParams<{ id?: string }>();
|
||||
|
||||
const [clickedSpanTags, setClickedSpanTags] = useState<pushDStree>([]);
|
||||
const [selectedSpan, setSelectedSpan] = useState({});
|
||||
const [clickedSpan, setClickedSpan] = useState(null);
|
||||
const [resetZoom, setResetZoom] = useState(false);
|
||||
const [sortedTreeData, setSortedTreeData] = useState<pushDStree[]>([]);
|
||||
|
||||
let sortedData = {};
|
||||
|
||||
const getSortedData = (treeData: pushDStree[], parent = {}) => {
|
||||
if (!isEmpty(treeData)) {
|
||||
if (treeData[0].id !== 'empty') {
|
||||
return Array.from(treeData).map((item, key) => {
|
||||
if (!isEmpty(item.children)) {
|
||||
getSortedData(item.children, item);
|
||||
sortedData = sortBy(item.children, (i) => i.startTime);
|
||||
treeData[key].children = sortedData;
|
||||
}
|
||||
if (!isEmpty(parent)) {
|
||||
treeData[key].parent = parent;
|
||||
}
|
||||
return treeData;
|
||||
});
|
||||
}
|
||||
return treeData;
|
||||
}
|
||||
};
|
||||
|
||||
const tree = spanToTreeUtil(props.traceItem[0].events);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
//sets span width based on value - which is mapped to duration
|
||||
fetchTraceItem(id || '')(dispatch);
|
||||
}, [dispatch, id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.traceItem) {
|
||||
const sortedData = getSortedData([tree]);
|
||||
setSortedTreeData(sortedData?.[0]);
|
||||
getSpanInfo(sortedData?.[0], spanId);
|
||||
// This is causing element to change ref. Can use both useRef or this approach.
|
||||
d3
|
||||
.select('#chart')
|
||||
.datum(tree)
|
||||
.call(chart)
|
||||
.sort((item) => item.startTime);
|
||||
}
|
||||
}, [props.traceItem]);
|
||||
// if this monitoring of props.traceItem.data is removed then zoom on click doesn't work
|
||||
// Doesn't work if only do initial check, works if monitor an element - as it may get updated in sometime
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isEmpty(sortedTreeData) &&
|
||||
sortedTreeData?.id !== 'empty' &&
|
||||
isEmpty(clickedSpanTags)
|
||||
) {
|
||||
setClickedSpanTags(sortedTreeData?.[0]);
|
||||
}
|
||||
}, [sortedTreeData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetZoom) {
|
||||
// This is causing element to change ref. Can use both useRef or this approach.
|
||||
d3
|
||||
.select('#chart')
|
||||
.datum(tree)
|
||||
.call(chart)
|
||||
.sort((item) => item.startTime);
|
||||
setResetZoom(false);
|
||||
}
|
||||
}, [resetZoom]);
|
||||
|
||||
const tip = d3Tip
|
||||
.default()
|
||||
.attr('class', 'd3-tip')
|
||||
.html(function (d: any) {
|
||||
return d.data.name + '<br>duration: ' + d.data.value / 1000000 + 'ms';
|
||||
});
|
||||
|
||||
const onClick = (z: any) => {
|
||||
setClickedSpanTags(z.data);
|
||||
setClickedSpan(z.data);
|
||||
setSelectedSpan([]);
|
||||
console.log(`Clicked on ${z.data.name}, id: "${z.id}"`);
|
||||
};
|
||||
|
||||
const setSpanTagsInfo = (z: any) => {
|
||||
setClickedSpanTags(z.data);
|
||||
};
|
||||
|
||||
const getSpanInfo = (data: [pushDStree], spanId: string): void => {
|
||||
if (resetZoom) {
|
||||
setSelectedSpan({});
|
||||
return;
|
||||
}
|
||||
if (data?.[0]?.id !== 'empty') {
|
||||
Array.from(data).map((item) => {
|
||||
if (item.id === spanId) {
|
||||
setSelectedSpan(item);
|
||||
setClickedSpanTags(item);
|
||||
return item;
|
||||
} else if (!isEmpty(item.children)) {
|
||||
getSpanInfo(item.children, spanId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const chart = flamegraph()
|
||||
.cellHeight(18)
|
||||
.transitionDuration(500)
|
||||
.inverted(true)
|
||||
.tooltip(tip)
|
||||
.minFrameSize(4)
|
||||
.elided(false)
|
||||
.differential(false)
|
||||
.sort((item) => item.startTime)
|
||||
//Use self value=true when we're using not using aggregated option, Which is not our case.
|
||||
// In that case it's doing step function sort of stuff thru computation.
|
||||
// Source flamegraph.js line 557 and 573.
|
||||
// .selfValue(true)
|
||||
.onClick(onClick)
|
||||
.width(800);
|
||||
|
||||
const handleResetZoom = (value) => {
|
||||
setResetZoom(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
|
||||
<Col md={18} sm={18}>
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<Card bodyStyle={{ padding: 24 }} style={{ height: 320 }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
Trace Graph component ID is {id}{' '}
|
||||
</div>
|
||||
<div id="chart" style={{ fontSize: 12, marginTop: 20 }}></div>
|
||||
</div>
|
||||
</Card>
|
||||
<Affix offsetTop={24}>
|
||||
<TraceGanttChartContainer id={'collapsable'}>
|
||||
<TraceGanttChart
|
||||
treeData={sortedTreeData}
|
||||
clickedSpan={clickedSpan}
|
||||
selectedSpan={selectedSpan}
|
||||
resetZoom={handleResetZoom}
|
||||
setSpanTagsInfo={setSpanTagsInfo}
|
||||
/>
|
||||
</TraceGanttChartContainer>
|
||||
</Affix>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col md={6} sm={6}>
|
||||
<Affix offsetTop={24}>
|
||||
<SelectedSpanDetails data={clickedSpanTags} />
|
||||
</Affix>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: AppState,
|
||||
): { traceItem: spansWSameTraceIDResponse } => {
|
||||
return { traceItem: state.traceItem };
|
||||
};
|
||||
|
||||
export const TraceGraph = connect(mapStateToProps, {
|
||||
fetchTraceItem: fetchTraceItem,
|
||||
})(_TraceGraph);
|
@ -1,76 +0,0 @@
|
||||
import { Table } from 'antd';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { pushDStree, traceResponseNew } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
||||
interface TraceGraphColumnProps {
|
||||
traces: traceResponseNew;
|
||||
}
|
||||
|
||||
interface TableDataSourceItem {
|
||||
key: string;
|
||||
operationName: string;
|
||||
startTime: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
const _TraceGraphColumn = (props: TraceGraphColumnProps) => {
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Start Time (UTC Time)',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
sorter: (a: any, b: any) => a.startTime - b.startTime,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => new Date(Math.round(value / 1000)).toUTCString(),
|
||||
},
|
||||
{
|
||||
title: 'Duration (in ms)',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
sorter: (a: any, b: any) => a.duration - b.duration,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value / 1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operationName',
|
||||
key: 'operationName',
|
||||
},
|
||||
];
|
||||
|
||||
const dataSource: TableDataSourceItem[] = [];
|
||||
|
||||
if (props.traces[0].events.length > 0) {
|
||||
props.traces[0].events.map(
|
||||
(item: (number | string | string[] | pushDStree[])[], index) => {
|
||||
if (
|
||||
typeof item[0] === 'number' &&
|
||||
typeof item[4] === 'string' &&
|
||||
typeof item[6] === 'string' &&
|
||||
typeof item[1] === 'string' &&
|
||||
typeof item[2] === 'string'
|
||||
)
|
||||
dataSource.push({
|
||||
startTime: item[0],
|
||||
operationName: item[4],
|
||||
duration: parseInt(item[6]),
|
||||
key: index.toString(),
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table dataSource={dataSource} columns={columns} size="middle" />;
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState): { traces: traceResponseNew } => {
|
||||
return { traces: state.traces };
|
||||
};
|
||||
|
||||
export const TraceGraphColumn = connect(mapStateToProps)(_TraceGraphColumn);
|
@ -1,5 +0,0 @@
|
||||
export { TraceGraph as default } from './TraceGraph';
|
||||
|
||||
// PNOTE
|
||||
// Because react.lazy doesn't work on named components
|
||||
// https://reactjs.org/docs/code-splitting.html#:~:text=Named%20Exports,t%20pull%20in%20unused%20components
|
@ -1,16 +0,0 @@
|
||||
import { Card as CardComponent, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const CustomGraphContainer = styled.div`
|
||||
min-height: 30vh;
|
||||
`;
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
.ant-card-body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CustomVisualizationsTitle = styled(Typography)`
|
||||
margin-bottom: 1rem;
|
||||
`;
|
@ -5,7 +5,6 @@ export const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 80vh;
|
||||
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
|
1
frontend/src/pages/TraceDetail/constants.ts
Normal file
1
frontend/src/pages/TraceDetail/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const SPAN_DETAILS_LEFT_COL_WIDTH = 350;
|
32
frontend/src/pages/TraceDetail/index.tsx
Normal file
32
frontend/src/pages/TraceDetail/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import getTraceItem from 'api/trace/getTraceItem';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Props as TraceDetailProps } from 'types/api/trace/getTraceItem';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { Typography } from 'antd';
|
||||
import TraceDetailContainer from 'container/TraceDetail';
|
||||
|
||||
const TraceDetail = (): JSX.Element => {
|
||||
const { id } = useParams<TraceDetailProps>();
|
||||
|
||||
const traceDetailResponse = useFetch(getTraceItem, {
|
||||
id,
|
||||
});
|
||||
|
||||
if (traceDetailResponse.error) {
|
||||
return (
|
||||
<Typography>
|
||||
{traceDetailResponse.errorMessage || 'Something went wrong'}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (traceDetailResponse.loading || traceDetailResponse.payload === undefined) {
|
||||
return <Spinner tip="Loading.." />;
|
||||
}
|
||||
|
||||
return <TraceDetailContainer response={traceDetailResponse.payload} />;
|
||||
};
|
||||
|
||||
export default TraceDetail;
|
@ -4,7 +4,5 @@ export * from './global';
|
||||
export * from './metrics';
|
||||
export * from './MetricsActions';
|
||||
export * from './serviceMap';
|
||||
export * from './traceFilters';
|
||||
export * from './traces';
|
||||
export * from './types';
|
||||
export * from './usage';
|
||||
|
@ -6,6 +6,7 @@ import getServiceOverview from 'api/metrics/getServiceOverview';
|
||||
import getTopEndPoints from 'api/metrics/getTopEndPoints';
|
||||
import { AxiosError } from 'axios';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getStep from 'lib/getStep';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
@ -64,7 +65,7 @@ export const GetInitialData = (
|
||||
end: maxTime,
|
||||
service: props.serviceName,
|
||||
start: minTime,
|
||||
step,
|
||||
step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }),
|
||||
}),
|
||||
getTopEndPoints({
|
||||
end: maxTime,
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { notification } from 'antd';
|
||||
import getSpansAggregate from 'api/trace/getSpansAggregate';
|
||||
import { Dispatch, Store } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_SPANS_AGGREEGATE } from 'types/actions/trace';
|
||||
import getSpansAggregate from 'api/trace/getSpansAggregate';
|
||||
import { Props as GetSpanAggregateProps } from 'types/api/trace/getSpanAggregate';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { notification } from 'antd';
|
||||
|
||||
export const GetSpansAggregate = (
|
||||
props: GetSpansAggregateProps,
|
||||
@ -52,6 +53,7 @@ export const GetSpansAggregate = (
|
||||
offset: props.current * props.pageSize - props.pageSize,
|
||||
selectedTags: props.selectedTags,
|
||||
isFilterExclude: traces.isFilterExclude,
|
||||
order: props.order,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
@ -112,4 +114,5 @@ export interface GetSpansAggregateProps {
|
||||
current: TraceReducer['spansAggregate']['currentPage'];
|
||||
pageSize: TraceReducer['spansAggregate']['pageSize'];
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
order: GetSpanAggregateProps['order'];
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
// Action creator must have a type and optionally a payload
|
||||
import { ActionTypes } from './types';
|
||||
|
||||
export interface TagItem {
|
||||
key: string;
|
||||
value: string;
|
||||
operator: 'equals' | 'contains' | 'regex';
|
||||
}
|
||||
|
||||
export interface LatencyValue {
|
||||
min: string;
|
||||
max: string;
|
||||
}
|
||||
|
||||
export interface TraceFilters {
|
||||
tags?: TagItem[];
|
||||
service?: string;
|
||||
latency?: LatencyValue;
|
||||
operation?: string;
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
//define interface for action. Action creator always returns object of this type
|
||||
export interface updateTraceFiltersAction {
|
||||
type: ActionTypes.updateTraceFilters;
|
||||
payload: TraceFilters;
|
||||
}
|
||||
|
||||
export const updateTraceFilters = (
|
||||
traceFilters: TraceFilters,
|
||||
): updateTraceFiltersAction => {
|
||||
return {
|
||||
type: ActionTypes.updateTraceFilters,
|
||||
payload: traceFilters,
|
||||
};
|
||||
};
|
||||
|
||||
//named export when you want to export multiple functions from the same file
|
@ -43,6 +43,10 @@ export interface pushDStree {
|
||||
startTime: number;
|
||||
tags: TraceTagItem[];
|
||||
children: pushDStree[];
|
||||
parent: pushDStree;
|
||||
serviceName: string;
|
||||
serviceColour: string;
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
export interface spanItem {
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { serviceMapItemAction, servicesAction } from './serviceMap';
|
||||
import { updateTraceFiltersAction } from './traceFilters';
|
||||
import { FetchTraceItemAction, FetchTracesAction } from './traces';
|
||||
import { getUsageDataAction } from './usage';
|
||||
|
||||
export enum ActionTypes {
|
||||
@ -13,10 +11,4 @@ export enum ActionTypes {
|
||||
fetchTraceItem = 'FETCH_TRACE_ITEM',
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| FetchTraceItemAction
|
||||
| FetchTracesAction
|
||||
| updateTraceFiltersAction
|
||||
| getUsageDataAction
|
||||
| servicesAction
|
||||
| serviceMapItemAction;
|
||||
export type Action = getUsageDataAction | servicesAction | serviceMapItemAction;
|
||||
|
@ -6,14 +6,10 @@ import globalTimeReducer from './global';
|
||||
import metricsReducers from './metric';
|
||||
import { ServiceMapReducer } from './serviceMap';
|
||||
import traceReducer from './trace';
|
||||
import TraceFilterReducer from './traceFilters';
|
||||
import { traceItemReducer } from './traces';
|
||||
import { usageDataReducer } from './usage';
|
||||
|
||||
const reducers = combineReducers({
|
||||
traceFilters: TraceFilterReducer,
|
||||
traces: traceReducer,
|
||||
traceItem: traceItemReducer,
|
||||
usageDate: usageDataReducer,
|
||||
globalTime: globalTimeReducer,
|
||||
serviceMap: ServiceMapReducer,
|
||||
|
@ -1,29 +0,0 @@
|
||||
import { TraceFilters } from 'store/actions/traceFilters';
|
||||
import { ActionTypes } from 'store/actions/types';
|
||||
|
||||
type ACTION = {
|
||||
type: ActionTypes;
|
||||
payload: TraceFilters;
|
||||
};
|
||||
|
||||
const initialState: TraceFilters = {
|
||||
service: '',
|
||||
tags: [],
|
||||
operation: '',
|
||||
latency: { min: '', max: '' },
|
||||
kind: '',
|
||||
};
|
||||
|
||||
const TraceFilterReducer = (
|
||||
state = initialState,
|
||||
action: ACTION,
|
||||
): TraceFilters => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.updateTraceFilters:
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default TraceFilterReducer;
|
@ -1,33 +0,0 @@
|
||||
import {
|
||||
Action,
|
||||
ActionTypes,
|
||||
spanList,
|
||||
spansWSameTraceIDResponse,
|
||||
traceResponseNew,
|
||||
} from 'store/actions';
|
||||
|
||||
// PNOTE - Initializing is a must for state variable otherwise it gives an error in reducer
|
||||
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;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const traceItemReducer = (
|
||||
state: spansWSameTraceIDResponse = { '0': spanlistinstance },
|
||||
action: Action,
|
||||
): spansWSameTraceIDResponse => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.fetchTraceItem:
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
@ -7,6 +7,7 @@ export interface Props {
|
||||
limit: number;
|
||||
offset: number;
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
order?: 'descending' | 'ascending';
|
||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||
}
|
||||
|
||||
|
50
frontend/src/types/api/trace/getTraceItem.ts
Normal file
50
frontend/src/types/api/trace/getTraceItem.ts
Normal file
@ -0,0 +1,50 @@
|
||||
export interface Props {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
[id: string]: {
|
||||
events: Span[];
|
||||
segmentID: string;
|
||||
columns: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export type Span = [
|
||||
number,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string | string[],
|
||||
string | string[],
|
||||
string | string[],
|
||||
ITraceTree[],
|
||||
];
|
||||
|
||||
export interface ITraceTree {
|
||||
id: string;
|
||||
name: string;
|
||||
value: number;
|
||||
time: number;
|
||||
startTime: number;
|
||||
tags: ITraceTag[];
|
||||
children: ITraceTree[];
|
||||
parent?: ITraceTree;
|
||||
serviceName: string;
|
||||
serviceColour: string;
|
||||
hasError?: boolean;
|
||||
event?: ITraceEvents[];
|
||||
}
|
||||
|
||||
export interface ITraceTag {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ITraceEvents {
|
||||
attributeMap: { event: string; [key: string]: string };
|
||||
name?: string;
|
||||
}
|
17
frontend/src/types/global.d.ts
vendored
Normal file
17
frontend/src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// For CSS
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
// For LESS
|
||||
declare module '*.module.less' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
// For SCSS
|
||||
declare module '*.module.scss' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
@ -1,39 +0,0 @@
|
||||
//You must first install the vis and react types 'npm install --save-dev @types/vis @types/react'
|
||||
declare module 'react-graph-vis' {
|
||||
import { Network, NetworkEvents, Options, Node, Edge, DataSet } from 'vis';
|
||||
import { Component } from 'react';
|
||||
|
||||
export { Network, NetworkEvents, Options, Node, Edge, DataSet } from 'vis';
|
||||
|
||||
export interface graphEvents {
|
||||
[event: NetworkEvents]: (params) => void;
|
||||
}
|
||||
|
||||
//Doesn't appear that this module supports passing in a vis.DataSet directly. Once it does graph can just use the Data object from vis.
|
||||
export interface graphData {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
}
|
||||
|
||||
export interface NetworkGraphProps {
|
||||
graph: graphData;
|
||||
options?: Options;
|
||||
events?: graphEvents;
|
||||
getNetwork?: (network: Network) => void;
|
||||
identifier?: string;
|
||||
style?: React.CSSProperties;
|
||||
getNodes?: (nodes: DataSet) => void;
|
||||
getEdges?: (edges: DataSet) => void;
|
||||
}
|
||||
|
||||
export interface NetworkGraphState {
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export default class NetworkGraph extends Component<
|
||||
NetworkGraphProps,
|
||||
NetworkGraphState
|
||||
> {
|
||||
render(): JSX.Element;
|
||||
}
|
||||
}
|
42
frontend/src/utils/getSpanTreeMetadata.ts
Normal file
42
frontend/src/utils/getSpanTreeMetadata.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { errorColor } from 'lib/getRandomColor';
|
||||
import { ITraceTree } from 'types/api/trace/getTraceItem';
|
||||
/**
|
||||
* Traverses the Span Tree data and returns the relevant meta data.
|
||||
* Metadata includes globalStart, globalEnd,
|
||||
*/
|
||||
export const getSpanTreeMetadata = (
|
||||
treeData: ITraceTree,
|
||||
spanServiceColours: { [key: string]: string },
|
||||
) => {
|
||||
let globalStart = Number.POSITIVE_INFINITY;
|
||||
let globalEnd = Number.NEGATIVE_INFINITY;
|
||||
let totalSpans = 0;
|
||||
let levels = 1;
|
||||
const traverse = (treeNode: ITraceTree, level: number = 0) => {
|
||||
if (!treeNode) {
|
||||
return;
|
||||
}
|
||||
totalSpans++;
|
||||
levels = Math.max(levels, level);
|
||||
const startTime = treeNode.startTime;
|
||||
const endTime = startTime + treeNode.value / 1e6;
|
||||
globalStart = Math.min(globalStart, startTime);
|
||||
globalEnd = Math.max(globalEnd, endTime);
|
||||
if (treeNode.hasError) {
|
||||
treeNode.serviceColour = errorColor;
|
||||
} else treeNode.serviceColour = spanServiceColours[treeNode.serviceName];
|
||||
for (const childNode of treeNode.children) {
|
||||
traverse(childNode, level + 1);
|
||||
}
|
||||
};
|
||||
traverse(treeData, 1);
|
||||
|
||||
return {
|
||||
globalStart,
|
||||
globalEnd,
|
||||
spread: globalEnd - globalStart,
|
||||
totalSpans,
|
||||
levels,
|
||||
treeData,
|
||||
};
|
||||
};
|
@ -1,9 +1,10 @@
|
||||
import { pushDStree, RefItem, span } from 'store/actions';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { ITraceTree, Span } from 'types/api/trace/getTraceItem';
|
||||
// PNOTE - should the data be taken from redux or only through props? - Directly as arguments
|
||||
|
||||
export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
|
||||
export const spanToTreeUtil = (originalList: Span[]): ITraceTree => {
|
||||
// Initializing tree. What should be returned is trace is empty? We should have better error handling
|
||||
let tree: pushDStree = {
|
||||
let tree: ITraceTree = {
|
||||
id: 'empty',
|
||||
name: 'default',
|
||||
value: 0,
|
||||
@ -11,8 +12,12 @@ export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
|
||||
startTime: 0,
|
||||
tags: [],
|
||||
children: [],
|
||||
serviceColour: '',
|
||||
serviceName: '',
|
||||
};
|
||||
|
||||
let spanlist = cloneDeep(originalList);
|
||||
|
||||
// let spans :spanItem[]= trace.spans;
|
||||
|
||||
if (spanlist) {
|
||||
@ -24,12 +29,15 @@ export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
|
||||
//May1
|
||||
//https://stackoverflow.com/questions/13315131/enforcing-the-type-of-the-indexed-members-of-a-typescript-object
|
||||
|
||||
const mapped_array: { [id: string]: span } = {};
|
||||
const mapped_array: { [id: string]: Span } = {};
|
||||
const originalListArray: { [id: string]: Span } = {};
|
||||
|
||||
for (let i = 0; i < spanlist.length; i++) {
|
||||
originalListArray[spanlist[i][1]] = originalList[i];
|
||||
|
||||
mapped_array[spanlist[i][1]] = spanlist[i];
|
||||
mapped_array[spanlist[i][1]][10] = []; //initialising the 10th element in the span data structure which is array
|
||||
// of type pushDStree
|
||||
mapped_array[spanlist[i][1]][10] = []; //initialising the 10th element in the Span data structure which is array
|
||||
// of type ITraceTree
|
||||
// console.log('IDs while creating mapped array')
|
||||
// console.log(`SpanID is ${spanlist[i][1]}\n`);
|
||||
}
|
||||
@ -54,15 +62,22 @@ export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
|
||||
}
|
||||
}
|
||||
|
||||
const push_object: pushDStree = {
|
||||
const push_object: ITraceTree = {
|
||||
id: child_span[1],
|
||||
name: child_span[3] + ': ' + child_span[4],
|
||||
name: child_span[4],
|
||||
value: parseInt(child_span[6]),
|
||||
time: parseInt(child_span[6]),
|
||||
startTime: child_span[0],
|
||||
tags: tags_temp,
|
||||
children: mapped_array[id][10],
|
||||
serviceName: child_span[3],
|
||||
hasError: !!child_span[11],
|
||||
serviceColour: '',
|
||||
event: originalListArray[id][10].map((e) => {
|
||||
return JSON.parse(decodeURIComponent(e || '{}')) || {};
|
||||
}),
|
||||
};
|
||||
|
||||
const referencesArr = mapped_array[id][9];
|
||||
let refArray = [];
|
||||
if (typeof referencesArr === 'string') {
|
||||
@ -70,7 +85,7 @@ export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
|
||||
} else {
|
||||
refArray = referencesArr;
|
||||
}
|
||||
const references: RefItem[] = [];
|
||||
const references = [];
|
||||
|
||||
refArray.forEach((element) => {
|
||||
element = element
|
||||
@ -116,5 +131,5 @@ export const spanToTreeUtil = (spanlist: span[]): pushDStree => {
|
||||
} // end of for loop
|
||||
} // end of if(spans)
|
||||
|
||||
return tree;
|
||||
return { ...tree };
|
||||
};
|
||||
|
6
frontend/src/utils/toFixed.ts
Normal file
6
frontend/src/utils/toFixed.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const toFixed = (input: number, fixedCount: number) => {
|
||||
if (input.toString().split('.').length > 1) {
|
||||
return input.toFixed(fixedCount);
|
||||
}
|
||||
return input;
|
||||
};
|
@ -18,7 +18,8 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"baseUrl": "./src",
|
||||
"downlevelIteration": true
|
||||
"downlevelIteration": true,
|
||||
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["./src"]
|
||||
|
@ -61,7 +61,15 @@ const config = {
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
@ -82,6 +90,9 @@ const config = {
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'less-loader',
|
||||
|
@ -74,7 +74,15 @@ const config = {
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
@ -95,6 +103,9 @@ const config = {
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'less-loader',
|
||||
|
@ -1773,6 +1773,25 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/color-convert@*":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22"
|
||||
integrity sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==
|
||||
dependencies:
|
||||
"@types/color-name" "*"
|
||||
|
||||
"@types/color-name@*":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/color@^3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.3.tgz#e6d8d72b7aaef4bb9fe80847c26c7c786191016d"
|
||||
integrity sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==
|
||||
dependencies:
|
||||
"@types/color-convert" "*"
|
||||
|
||||
"@types/compression-webpack-plugin@^9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/compression-webpack-plugin/-/compression-webpack-plugin-9.0.0.tgz#d7d504e2268e84e1413a99c072d6ff9aee31f213"
|
||||
@ -2146,6 +2165,11 @@
|
||||
jest-diff "^26.0.0"
|
||||
pretty-format "^26.0.0"
|
||||
|
||||
"@types/js-cookie@^2.2.6":
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
|
||||
integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||
version "7.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||
@ -2621,6 +2645,11 @@
|
||||
dependencies:
|
||||
lodash "^4"
|
||||
|
||||
"@xobotyi/scrollbar-width@^1.9.5":
|
||||
version "1.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
|
||||
integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==
|
||||
|
||||
"@xtuc/ieee754@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||
@ -3537,6 +3566,11 @@ bcrypt-pbkdf@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/bezier-js/-/bezier-js-4.1.1.tgz#414df656833104e86765c0fa5e31439fb3e83a34"
|
||||
integrity sha512-oVOS6SSFFFlfnZdzC+lsfvhs/RRcbxJ47U04M4s5QIBaJmr3YWmTIL3qmrOK9uW+nUUcl9Jccmo/xpTrG+bBoQ==
|
||||
|
||||
big.js@^3.1.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
|
||||
integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
|
||||
|
||||
big.js@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
@ -3852,7 +3886,7 @@ chalk@2.1.0:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^4.0.0"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.4.2:
|
||||
chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
@ -3897,6 +3931,21 @@ check-more-types@^2.24.0:
|
||||
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
|
||||
integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=
|
||||
|
||||
"chokidar@>=3.0.0 <4.0.0":
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.2"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.6.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
chokidar@^3.5.1, chokidar@^3.5.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
|
||||
@ -4059,11 +4108,27 @@ color-name@1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
color-name@~1.1.4:
|
||||
color-name@^1.0.0, color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-string@^1.9.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa"
|
||||
integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==
|
||||
dependencies:
|
||||
color-name "^1.0.0"
|
||||
simple-swizzle "^0.2.2"
|
||||
|
||||
color@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884"
|
||||
integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==
|
||||
dependencies:
|
||||
color-convert "^2.0.1"
|
||||
color-string "^1.9.0"
|
||||
|
||||
colord@^2.9.1:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e"
|
||||
@ -4220,7 +4285,7 @@ copy-descriptor@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||
|
||||
copy-to-clipboard@^3.2.0:
|
||||
copy-to-clipboard@^3.2.0, copy-to-clipboard@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
|
||||
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
|
||||
@ -4361,6 +4426,14 @@ css-declaration-sorter@^6.0.3:
|
||||
dependencies:
|
||||
timsort "^0.3.0"
|
||||
|
||||
css-in-js-utils@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99"
|
||||
integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==
|
||||
dependencies:
|
||||
hyphenate-style-name "^1.0.2"
|
||||
isobject "^3.0.1"
|
||||
|
||||
css-loader@4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e"
|
||||
@ -4392,6 +4465,13 @@ css-minimizer-webpack-plugin@^3.2.0:
|
||||
serialize-javascript "^6.0.0"
|
||||
source-map "^0.6.1"
|
||||
|
||||
css-parse@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4"
|
||||
integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=
|
||||
dependencies:
|
||||
css "^2.0.0"
|
||||
|
||||
css-select@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067"
|
||||
@ -4403,6 +4483,14 @@ css-select@^4.1.3:
|
||||
domutils "^2.6.0"
|
||||
nth-check "^2.0.0"
|
||||
|
||||
css-selector-tokenizer@^0.7.0:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1"
|
||||
integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
fastparse "^1.1.2"
|
||||
|
||||
css-to-react-native@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756"
|
||||
@ -4430,6 +4518,16 @@ css.escape@^1.5.1:
|
||||
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||
integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
|
||||
|
||||
css@^2.0.0:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
|
||||
integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
source-map "^0.6.1"
|
||||
source-map-resolve "^0.5.2"
|
||||
urix "^0.1.0"
|
||||
|
||||
css@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d"
|
||||
@ -4518,7 +4616,7 @@ cssstyle@^2.3.0:
|
||||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
csstype@^3.0.2:
|
||||
csstype@^3.0.2, csstype@^3.0.6:
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
|
||||
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
|
||||
@ -5172,6 +5270,13 @@ debug@ngokevin/debug#noTimestamp:
|
||||
version "2.2.0"
|
||||
resolved "https://codeload.github.com/ngokevin/debug/tar.gz/ef5f8e66d49ce8bc64c6f282c15f8b7164409e3a"
|
||||
|
||||
debug@~3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
@ -5467,6 +5572,11 @@ dotenv@8.2.0:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
|
||||
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||
|
||||
dotenv@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||
|
||||
dtype@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dtype/-/dtype-2.0.0.tgz#cd052323ce061444ecd2e8f5748f69a29be28434"
|
||||
@ -5505,6 +5615,11 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
emojis-list@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
|
||||
integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k=
|
||||
|
||||
emojis-list@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
|
||||
@ -5561,6 +5676,13 @@ error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
error-stack-parser@^2.0.6:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.7.tgz#b0c6e2ce27d0495cf78ad98715e0cad1219abb57"
|
||||
integrity sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==
|
||||
dependencies:
|
||||
stackframe "^1.1.1"
|
||||
|
||||
es-abstract@^1.19.0, es-abstract@^1.19.1:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
|
||||
@ -6094,11 +6216,26 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fast-shallow-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b"
|
||||
integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==
|
||||
|
||||
fastest-levenshtein@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
|
||||
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
|
||||
|
||||
fastest-stable-stringify@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76"
|
||||
integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==
|
||||
|
||||
fastparse@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
|
||||
integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
|
||||
@ -6374,6 +6511,13 @@ gauge@~2.7.3:
|
||||
strip-ansi "^3.0.1"
|
||||
wide-align "^1.1.0"
|
||||
|
||||
generic-names@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-1.0.3.tgz#2d786a121aee508876796939e8e3bff836c20917"
|
||||
integrity sha1-LXhqEhruUIh2eWk56OO/+DbCCRc=
|
||||
dependencies:
|
||||
loader-utils "^0.2.16"
|
||||
|
||||
gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||
@ -6478,7 +6622,7 @@ glob-to-regexp@^0.4.1:
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
||||
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
|
||||
@ -6844,6 +6988,11 @@ husky@4.3.8:
|
||||
slash "^3.0.0"
|
||||
which-pm-runs "^1.0.0"
|
||||
|
||||
hyphenate-style-name@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
|
||||
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
|
||||
|
||||
iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.4:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@ -6851,6 +7000,13 @@ iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.4:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
icss-utils@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-3.0.1.tgz#ee70d3ae8cac38c6be5ed91e851b27eed343ad0f"
|
||||
integrity sha1-7nDTroysOMa+XtkehRsn7tNDrQ8=
|
||||
dependencies:
|
||||
postcss "^6.0.2"
|
||||
|
||||
icss-utils@^4.0.0, icss-utils@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
|
||||
@ -6858,6 +7014,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
|
||||
dependencies:
|
||||
postcss "^7.0.14"
|
||||
|
||||
icss-utils@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||
|
||||
ieee754@^1.1.13:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
@ -6889,6 +7050,11 @@ image-size@~0.5.0:
|
||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
|
||||
integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=
|
||||
|
||||
immutable@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
||||
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
||||
|
||||
import-fresh@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
|
||||
@ -6956,6 +7122,13 @@ ini@~1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
inline-style-prefixer@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-6.0.1.tgz#c5c0e43ba8831707afc5f5bbfd97edf45c1fa7ae"
|
||||
integrity sha512-AsqazZ8KcRzJ9YPN1wMH2aNM7lkWQ8tSPrW5uDk1ziYwiAPWSZnUsC7lfZq+BDqLqz0B4Pho5wscWcJzVvRzDQ==
|
||||
dependencies:
|
||||
css-in-js-utils "^2.0.0"
|
||||
|
||||
internal-slot@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
||||
@ -7027,6 +7200,11 @@ is-arrayish@^0.2.1:
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
|
||||
|
||||
is-arrayish@^0.3.1:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
|
||||
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
|
||||
|
||||
is-bigint@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
|
||||
@ -7806,6 +7984,11 @@ jest@26.6.0:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^26.6.0"
|
||||
|
||||
js-cookie@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
|
||||
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
@ -7909,6 +8092,11 @@ json2mq@^0.2.0:
|
||||
dependencies:
|
||||
string-convert "^0.2.0"
|
||||
|
||||
json5@^0.5.0:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
|
||||
integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
|
||||
|
||||
json5@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
||||
@ -8020,7 +8208,7 @@ less-plugin-npm-import@^2.1.0:
|
||||
promise "~7.0.1"
|
||||
resolve "~1.1.6"
|
||||
|
||||
less@^4.1.2:
|
||||
less@^4.1.1, less@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/less/-/less-4.1.2.tgz#6099ee584999750c2624b65f80145f8674e4b4b0"
|
||||
integrity sha512-EoQp/Et7OSOVu0aJknJOtlXZsnr8XE8KwuzTHOLeVSEx8pVWUICc8Q0VYRHgzyjX78nMEyC/oztWFbgyhtNfDA==
|
||||
@ -8058,7 +8246,7 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
lilconfig@^2.0.3:
|
||||
lilconfig@^2.0.3, lilconfig@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
|
||||
integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==
|
||||
@ -8122,6 +8310,16 @@ loader-runner@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
|
||||
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
|
||||
|
||||
loader-utils@^0.2.16:
|
||||
version "0.2.17"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
|
||||
integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=
|
||||
dependencies:
|
||||
big.js "^3.1.3"
|
||||
emojis-list "^2.0.0"
|
||||
json5 "^0.5.0"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
loader-utils@^1.1.0, loader-utils@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
|
||||
@ -8175,6 +8373,11 @@ lodash-es@^4.17.21:
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
@ -8215,7 +8418,7 @@ lodash.uniq@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@^4, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
|
||||
lodash@^4, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@ -8480,6 +8683,11 @@ mkdirp@^0.5.3, mkdirp@^0.5.5:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
mkdirp@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
moment@>=2.13.0, moment@^2.24.0, moment@^2.25.3:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
@ -8533,6 +8741,20 @@ nan@^2.14.0:
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
|
||||
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
|
||||
|
||||
nano-css@^5.3.1:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.4.tgz#40af6a83a76f84204f346e8ccaa9169cdae9167b"
|
||||
integrity sha512-wfcviJB6NOxDIDfr7RFn/GlaN7I/Bhe4d39ZRCJ3xvZX60LVe2qZ+rDqM49nm4YT81gAjzS+ZklhKP/Gnfnubg==
|
||||
dependencies:
|
||||
css-tree "^1.1.2"
|
||||
csstype "^3.0.6"
|
||||
fastest-stable-stringify "^2.0.2"
|
||||
inline-style-prefixer "^6.0.0"
|
||||
rtl-css-js "^1.14.0"
|
||||
sourcemap-codec "^1.4.8"
|
||||
stacktrace-js "^2.0.2"
|
||||
stylis "^4.0.6"
|
||||
|
||||
nanoid@^2.0.3:
|
||||
version "2.1.11"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
|
||||
@ -8543,6 +8765,11 @@ nanoid@^3.1.30:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362"
|
||||
integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==
|
||||
|
||||
nanoid@^3.2.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
|
||||
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
@ -9092,14 +9319,11 @@ parse-json@^5.0.0:
|
||||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
|
||||
parse-node-version@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
|
||||
integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
|
||||
|
||||
parse5@6.0.1:
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||
@ -9108,7 +9332,6 @@ parse5-htmlparser2-tree-adapter@^6.0.1:
|
||||
parse5 "^6.0.1"
|
||||
|
||||
parse5@6.0.1, parse5@^6.0.1:
|
||||
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||
@ -9344,6 +9567,41 @@ postcss-discard-overridden@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6"
|
||||
integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==
|
||||
|
||||
postcss-filter-plugins@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-3.0.1.tgz#9d226e946d56542ab7c26123053459a331df545d"
|
||||
integrity sha512-tRKbW4wWBEkSSFuJtamV2wkiV9rj6Yy7P3Y13+zaynlPEEZt8EgYKn3y/RBpMeIhNmHXFlSdzofml65hD5OafA==
|
||||
dependencies:
|
||||
postcss "^6.0.14"
|
||||
|
||||
postcss-icss-keyframes@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-icss-keyframes/-/postcss-icss-keyframes-0.2.1.tgz#80c4455e0112b0f2f9c3c05ac7515062bb9ff295"
|
||||
integrity sha1-gMRFXgESsPL5w8Bax1FQYruf8pU=
|
||||
dependencies:
|
||||
icss-utils "^3.0.1"
|
||||
postcss "^6.0.2"
|
||||
postcss-value-parser "^3.3.0"
|
||||
|
||||
postcss-icss-selectors@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss-icss-selectors/-/postcss-icss-selectors-2.0.3.tgz#27fa1afcaab6c602c866cbb298f3218e9bc1c9b3"
|
||||
integrity sha1-J/oa/Kq2xgLIZsuymPMhjpvBybM=
|
||||
dependencies:
|
||||
css-selector-tokenizer "^0.7.0"
|
||||
generic-names "^1.0.2"
|
||||
icss-utils "^3.0.1"
|
||||
lodash "^4.17.4"
|
||||
postcss "^6.0.2"
|
||||
|
||||
postcss-load-config@^3.0.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.3.tgz#21935b2c43b9a86e6581a576ca7ee1bde2bd1d23"
|
||||
integrity sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==
|
||||
dependencies:
|
||||
lilconfig "^2.0.4"
|
||||
yaml "^1.10.2"
|
||||
|
||||
postcss-merge-longhand@^5.0.4:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz#41f4f3270282ea1a145ece078b7679f0cef21c32"
|
||||
@ -9544,6 +9802,11 @@ postcss-unique-selectors@^5.0.2:
|
||||
alphanum-sort "^1.0.2"
|
||||
postcss-selector-parser "^6.0.5"
|
||||
|
||||
postcss-value-parser@^3.3.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
|
||||
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
|
||||
|
||||
postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
@ -9557,6 +9820,24 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
|
||||
picocolors "^0.2.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
postcss@^6.0.14, postcss@^6.0.2:
|
||||
version "6.0.23"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
|
||||
integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^5.4.0"
|
||||
|
||||
postcss@^8.3.0:
|
||||
version "8.4.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1"
|
||||
integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==
|
||||
dependencies:
|
||||
nanoid "^3.2.0"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.3.5:
|
||||
version "8.4.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.4.tgz#d53d4ec6a75fd62557a66bb41978bf47ff0c2869"
|
||||
@ -10321,6 +10602,31 @@ react-router@5.2.1:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-universal-interface@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b"
|
||||
integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==
|
||||
|
||||
react-use@^17.3.2:
|
||||
version "17.3.2"
|
||||
resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.3.2.tgz#448abf515f47c41c32455024db28167cb6e53be8"
|
||||
integrity sha512-bj7OD0/1wL03KyWmzFXAFe425zziuTf7q8olwCYBfOeFHY1qfO1FAMjROQLsLZYwG4Rx63xAfb7XAbBrJsZmEw==
|
||||
dependencies:
|
||||
"@types/js-cookie" "^2.2.6"
|
||||
"@xobotyi/scrollbar-width" "^1.9.5"
|
||||
copy-to-clipboard "^3.3.1"
|
||||
fast-deep-equal "^3.1.3"
|
||||
fast-shallow-equal "^1.0.0"
|
||||
js-cookie "^2.2.1"
|
||||
nano-css "^5.3.1"
|
||||
react-universal-interface "^0.6.2"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
screenfull "^5.1.0"
|
||||
set-harmonic-interval "^1.0.1"
|
||||
throttle-debounce "^3.0.1"
|
||||
ts-easing "^0.2.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
react-vis@^1.11.7:
|
||||
version "1.11.7"
|
||||
resolved "https://registry.yarnpkg.com/react-vis/-/react-vis-1.11.7.tgz#909902af00158895d14da1adfe1d0dc0045228ff"
|
||||
@ -10555,6 +10861,11 @@ requires-port@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
reserved-words@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
|
||||
integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=
|
||||
|
||||
resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
@ -10653,6 +10964,13 @@ rsvp@^4.8.4:
|
||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
|
||||
|
||||
rtl-css-js@^1.14.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.15.0.tgz#680ed816e570a9ebccba9e1cd0f202c6a8bb2dc0"
|
||||
integrity sha512-99Cu4wNNIhrI10xxUaABHsdDqzalrSRTie4GeCmbGVuehm4oj+fIy8fTzB+16pmKe8Bv9rl+hxIBez6KxExTew==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||
@ -10689,7 +11007,7 @@ safe-regex@^1.1.0:
|
||||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
@ -10709,7 +11027,16 @@ sane@^4.0.3:
|
||||
minimist "^1.1.1"
|
||||
walker "~1.0.5"
|
||||
|
||||
sax@>=0.6.0, sax@^1.2.4:
|
||||
sass@^1.32.13:
|
||||
version "1.49.7"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.7.tgz#22a86a50552b9b11f71404dfad1b9ff44c6b0c49"
|
||||
integrity sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
@ -10757,6 +11084,11 @@ schema-utils@^4.0.0:
|
||||
ajv-formats "^2.1.1"
|
||||
ajv-keywords "^5.0.0"
|
||||
|
||||
screenfull@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba"
|
||||
integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==
|
||||
|
||||
scroll-into-view-if-needed@^2.2.25:
|
||||
version "2.2.28"
|
||||
resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz#5a15b2f58a52642c88c8eca584644e01703d645a"
|
||||
@ -10869,6 +11201,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||
|
||||
set-harmonic-interval@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249"
|
||||
integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==
|
||||
|
||||
set-value@^2.0.0, set-value@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
|
||||
@ -10967,6 +11304,13 @@ simple-get@^3.0.3:
|
||||
once "^1.3.1"
|
||||
simple-concat "^1.0.0"
|
||||
|
||||
simple-swizzle@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
|
||||
integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=
|
||||
dependencies:
|
||||
is-arrayish "^0.3.1"
|
||||
|
||||
sirv@^1.0.7:
|
||||
version "1.0.19"
|
||||
resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49"
|
||||
@ -11048,12 +11392,17 @@ source-list-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
||||
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
||||
source-map-js@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
|
||||
integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
|
||||
|
||||
source-map-resolve@^0.5.0:
|
||||
source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
|
||||
integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==
|
||||
@ -11085,6 +11434,11 @@ source-map-url@^0.4.0:
|
||||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
|
||||
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
|
||||
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
|
||||
|
||||
source-map@^0.5.0, source-map@^0.5.6:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
@ -11100,6 +11454,11 @@ source-map@^0.7.3, source-map@~0.7.2:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
sourcemap-codec@^1.4.8:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
||||
@ -11181,6 +11540,13 @@ stable@^0.1.8:
|
||||
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
|
||||
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
|
||||
|
||||
stack-generator@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36"
|
||||
integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==
|
||||
dependencies:
|
||||
stackframe "^1.1.1"
|
||||
|
||||
stack-utils@^2.0.2:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
|
||||
@ -11188,6 +11554,28 @@ stack-utils@^2.0.2:
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
stackframe@^1.1.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.1.tgz#1033a3473ee67f08e2f2fc8eba6aef4f845124e1"
|
||||
integrity sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==
|
||||
|
||||
stacktrace-gps@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a"
|
||||
integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==
|
||||
dependencies:
|
||||
source-map "0.5.6"
|
||||
stackframe "^1.1.1"
|
||||
|
||||
stacktrace-js@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b"
|
||||
integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==
|
||||
dependencies:
|
||||
error-stack-parser "^2.0.6"
|
||||
stack-generator "^2.0.5"
|
||||
stacktrace-gps "^3.0.4"
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||
@ -11385,6 +11773,25 @@ stylehacks@^5.0.1:
|
||||
browserslist "^4.16.0"
|
||||
postcss-selector-parser "^6.0.4"
|
||||
|
||||
stylis@^4.0.6:
|
||||
version "4.0.13"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
|
||||
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
|
||||
|
||||
stylus@^0.54.8:
|
||||
version "0.54.8"
|
||||
resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147"
|
||||
integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==
|
||||
dependencies:
|
||||
css-parse "~2.0.0"
|
||||
debug "~3.1.0"
|
||||
glob "^7.1.6"
|
||||
mkdirp "~1.0.4"
|
||||
safer-buffer "^2.1.2"
|
||||
sax "~1.2.4"
|
||||
semver "^6.3.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
super-animejs@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/super-animejs/-/super-animejs-3.1.0.tgz#59435946faafe880710e348cf24ad3126e45aed1"
|
||||
@ -11402,7 +11809,7 @@ supports-color@^4.0.0:
|
||||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||
supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
@ -11596,6 +12003,11 @@ throat@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
|
||||
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
|
||||
|
||||
throttle-debounce@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
|
||||
integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
|
||||
|
||||
throttleit@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
|
||||
@ -11724,6 +12136,11 @@ tr46@^2.1.0:
|
||||
dependencies:
|
||||
punycode "^2.1.1"
|
||||
|
||||
ts-easing@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
|
||||
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
|
||||
|
||||
ts-node@^10.2.1:
|
||||
version "10.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7"
|
||||
@ -11766,7 +12183,7 @@ tslib@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.3, tslib@^2.3.0:
|
||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
@ -11849,6 +12266,25 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript-plugin-css-modules@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/typescript-plugin-css-modules/-/typescript-plugin-css-modules-3.4.0.tgz#4ff6905d88028684d1608c05c62cb6346e5548cc"
|
||||
integrity sha512-2MdjfSg4MGex1csCWRUwKD+MpgnvcvLLr9bSAMemU/QYGqBsXdez0cc06H/fFhLtRoKJjXg6PSTur3Gy1Umhpw==
|
||||
dependencies:
|
||||
dotenv "^10.0.0"
|
||||
icss-utils "^5.1.0"
|
||||
less "^4.1.1"
|
||||
lodash.camelcase "^4.3.0"
|
||||
postcss "^8.3.0"
|
||||
postcss-filter-plugins "^3.0.1"
|
||||
postcss-icss-keyframes "^0.2.1"
|
||||
postcss-icss-selectors "^2.0.3"
|
||||
postcss-load-config "^3.0.1"
|
||||
reserved-words "^0.1.2"
|
||||
sass "^1.32.13"
|
||||
stylus "^0.54.8"
|
||||
tsconfig-paths "^3.9.0"
|
||||
|
||||
typescript@^4.0.5:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
|
||||
|
@ -205,17 +205,8 @@ func (item *SearchSpanReponseItem) GetValues() []interface{} {
|
||||
for _, item := range references {
|
||||
referencesStringArray = append(referencesStringArray, item.toString())
|
||||
}
|
||||
var errorEvent map[string]interface{}
|
||||
for _, e := range item.Events {
|
||||
json.Unmarshal([]byte(e), &errorEvent)
|
||||
if errorEvent["name"] == "exception" {
|
||||
break
|
||||
} else {
|
||||
errorEvent = nil
|
||||
}
|
||||
}
|
||||
|
||||
returnArray := []interface{}{int64(timeObj.UnixNano() / 1000000), item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), item.TagsKeys, item.TagsValues, referencesStringArray, errorEvent, item.HasError}
|
||||
returnArray := []interface{}{int64(timeObj.UnixNano() / 1000000), item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), item.TagsKeys, item.TagsValues, referencesStringArray, item.Events, item.HasError}
|
||||
|
||||
return returnArray
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ const (
|
||||
)
|
||||
|
||||
const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz"
|
||||
const IP_NOT_FOUND_PLACEHOLDER = "NA"
|
||||
|
||||
var telemetry *Telemetry
|
||||
var once sync.Once
|
||||
@ -59,7 +60,7 @@ func createTelemetry() {
|
||||
// Get preferred outbound ip of this machine
|
||||
func getOutboundIP() string {
|
||||
|
||||
ip := []byte("NA")
|
||||
ip := []byte(IP_NOT_FOUND_PLACEHOLDER)
|
||||
resp, err := http.Get("https://api.ipify.org?format=text")
|
||||
|
||||
if err != nil {
|
||||
@ -116,7 +117,7 @@ func (a *Telemetry) SendEvent(event string, data map[string]interface{}) {
|
||||
}
|
||||
|
||||
userId := a.ipAddress
|
||||
if a.isTelemetryAnonymous() {
|
||||
if a.isTelemetryAnonymous() || userId == IP_NOT_FOUND_PLACEHOLDER {
|
||||
userId = a.GetDistinctId()
|
||||
}
|
||||
|
||||
|
27
sample-apps/hotrod/README.md
Normal file
27
sample-apps/hotrod/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# HotROD Sample Application (Kubernetes)
|
||||
|
||||
Follow the steps in this section to install a sample application named HotR.O.D, and generate tracing data.
|
||||
|
||||
```console
|
||||
kubectl create ns sample-application
|
||||
|
||||
kubectl -n sample-application apply -f https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod.yaml
|
||||
```
|
||||
|
||||
In case, you have installed SigNoz in namespace other than `platform` or selected Helm release name other than `my-release`, follow the steps below:
|
||||
|
||||
```console
|
||||
export HELM_RELEASE=my-release-2
|
||||
export SIGNOZ_NAMESPACE=platform-2
|
||||
export HOTROD_NAMESPACE=sample-application-2
|
||||
|
||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-install.sh | bash
|
||||
```
|
||||
|
||||
To delete sample application:
|
||||
|
||||
```console
|
||||
export HOTROD_NAMESPACE=sample-application-2
|
||||
|
||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-delete.sh | bash
|
||||
```
|
20
sample-apps/hotrod/hotrod-delete.sh
Executable file
20
sample-apps/hotrod/hotrod-delete.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")";
|
||||
|
||||
HOTROD_NAMESPACE=${HOTROD_NAMESPACE:-"sample-application"}
|
||||
|
||||
if [[ "${HOTROD_NAMESPACE}" == "default" || "${HOTROD_NAMESPACE}" == "kube-system" || "${HOTROD_NAMESPACE}" == "platform" ]]; then
|
||||
echo "Default k8s namespace and SigNoz namespace must not be deleted"
|
||||
echo "Deleting components only"
|
||||
kubectl delete --namespace="${HOTROD_NAMESPACE}" -f <(cat hotrod-template.yaml || curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-template.yaml)
|
||||
else
|
||||
echo "Delete HotROD sample app namespace ${HOTROD_NAMESPACE}"
|
||||
kubectl delete namespace "${HOTROD_NAMESPACE}"
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to delete HotROD sample application"
|
||||
else
|
||||
echo "✅ Succesfully deleted HotROD sample application"
|
||||
fi
|
52
sample-apps/hotrod/hotrod-install.sh
Executable file
52
sample-apps/hotrod/hotrod-install.sh
Executable file
@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")";
|
||||
|
||||
# Namespace to install sample app
|
||||
HOTROD_NAMESPACE=${HOTROD_NAMESPACE:-"sample-application"}
|
||||
SIGNOZ_NAMESPACE="${SIGNOZ_NAMESPACE:-platform}"
|
||||
|
||||
# HotROD's docker image
|
||||
if [[ -z $HOTROD_IMAGE ]]; then
|
||||
HOTROD_REPO="${HOTROD_REPO:-jaegertracing/example-hotrod}"
|
||||
HOTROD_TAG="${HOTROD_TAG:-1.30}"
|
||||
HOTROD_IMAGE="${HOTROD_REPO}:${HOTROD_TAG}"
|
||||
fi
|
||||
|
||||
# Locust's docker image
|
||||
if [[ -z $LOCUST_IMAGE ]]; then
|
||||
LOCUST_REPO="${LOCUST_REPO:-grubykarol/locust}"
|
||||
LOCUST_TAG="${LOCUST_TAG:-0.8.1-py3.6}"
|
||||
LOCUST_IMAGE="${LOCUST_REPO}:${LOCUST_TAG}"
|
||||
fi
|
||||
|
||||
# Helm release name
|
||||
HELM_RELEASE="${HELM_RELEASE:-my-release}"
|
||||
|
||||
# Otel Collector service address
|
||||
if [[ -z $JAEGER_ENDPOINT ]]; then
|
||||
if [[ "$HELM_RELEASE" == *"signoz"* ]]; then
|
||||
JAEGER_ENDPOINT="http://${HELM_RELEASE}-otel-collector.${SIGNOZ_NAMESPACE}.svc.cluster.local:14268/api/traces"
|
||||
else
|
||||
JAEGER_ENDPOINT="http://${HELM_RELEASE}-signoz-otel-collector.${SIGNOZ_NAMESPACE}.svc.cluster.local:14268/api/traces"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create namespace for sample application if does not exist
|
||||
kubectl create namespace "$HOTROD_NAMESPACE" --save-config --dry-run -o yaml 2>/dev/null | kubectl apply -f -
|
||||
|
||||
# Setup sample apps into specified namespace
|
||||
kubectl apply --namespace="${HOTROD_NAMESPACE}" -f <( \
|
||||
(cat hotrod-template.yaml 2>/dev/null || curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-template.yaml) | \
|
||||
HOTROD_NAMESPACE="${HOTROD_NAMESPACE}" \
|
||||
HOTROD_IMAGE="${HOTROD_IMAGE}" \
|
||||
LOCUST_IMAGE="${LOCUST_IMAGE}" \
|
||||
JAEGER_ENDPOINT="${JAEGER_ENDPOINT}" \
|
||||
envsubst \
|
||||
)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to deploy HotROD sample application"
|
||||
else
|
||||
echo "✅ Succesfully deployed HotROD sample application"
|
||||
fi
|
223
sample-apps/hotrod/hotrod-template.yaml
Normal file
223
sample-apps/hotrod/hotrod-template.yaml
Normal file
@ -0,0 +1,223 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: locust-cm
|
||||
data:
|
||||
ATTACKED_HOST: http://hotrod:8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: scripts-cm
|
||||
data:
|
||||
locustfile.py: |
|
||||
from locust import HttpLocust, TaskSet, task
|
||||
class UserTasks(TaskSet):
|
||||
@task
|
||||
def rachel(self):
|
||||
self.client.get("/dispatch?customer=123&nonse=0.6308392664170006")
|
||||
@task
|
||||
def trom(self):
|
||||
self.client.get("/dispatch?customer=392&nonse=0.015296363321630757")
|
||||
@task
|
||||
def japanese(self):
|
||||
self.client.get("/dispatch?customer=731&nonse=0.8022286220408668")
|
||||
@task
|
||||
def coffee(self):
|
||||
self.client.get("/dispatch?customer=567&nonse=0.0022220379420636593")
|
||||
class WebsiteUser(HttpLocust):
|
||||
task_set = UserTasks
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
service: hotrod
|
||||
name: hotrod
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
service: hotrod
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
service: hotrod
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- all
|
||||
env:
|
||||
- name: JAEGER_ENDPOINT
|
||||
value: ${JAEGER_ENDPOINT}
|
||||
image: ${HOTROD_IMAGE}
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: hotrod
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
service: hotrod
|
||||
name: hotrod
|
||||
spec:
|
||||
ports:
|
||||
- name: "8080"
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
service: hotrod
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
deployment.kubernetes.io/revision: "1"
|
||||
labels:
|
||||
role: locust-master
|
||||
name: locust-master
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
role: locust-master
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
role: locust-master
|
||||
spec:
|
||||
containers:
|
||||
- image: ${LOCUST_IMAGE}
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: locust-master
|
||||
env:
|
||||
- name: ATTACKED_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: locust-cm
|
||||
key: ATTACKED_HOST
|
||||
- name: LOCUST_MODE
|
||||
value: MASTER
|
||||
- name: LOCUST_OPTS
|
||||
value: --print-stats
|
||||
volumeMounts:
|
||||
- mountPath: /locust
|
||||
name: locust-scripts
|
||||
ports:
|
||||
- containerPort: 5557
|
||||
name: comm
|
||||
- containerPort: 5558
|
||||
name: comm-plus-1
|
||||
- containerPort: 8089
|
||||
name: web-ui
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 200Mi
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- name: locust-scripts
|
||||
configMap:
|
||||
name: scripts-cm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
role: locust-master
|
||||
name: locust-master
|
||||
spec:
|
||||
ports:
|
||||
- port: 5557
|
||||
name: communication
|
||||
- port: 5558
|
||||
name: communication-plus-1
|
||||
- port: 8089
|
||||
targetPort: 8089
|
||||
name: web-ui
|
||||
selector:
|
||||
role: locust-master
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
deployment.kubernetes.io/revision: "1"
|
||||
labels:
|
||||
role: locust-slave
|
||||
name: locust-slave
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
role: locust-slave
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
role: locust-slave
|
||||
spec:
|
||||
containers:
|
||||
- image: ${LOCUST_IMAGE}
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: locust-slave
|
||||
env:
|
||||
- name: ATTACKED_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: locust-cm
|
||||
key: ATTACKED_HOST
|
||||
- name: LOCUST_MODE
|
||||
value: SLAVE
|
||||
- name: LOCUST_MASTER
|
||||
value: locust-master
|
||||
volumeMounts:
|
||||
- mountPath: /locust
|
||||
name: locust-scripts
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 200Mi
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- name: locust-scripts
|
||||
configMap:
|
||||
name: scripts-cm
|
Loading…
x
Reference in New Issue
Block a user