From eb63b6da2a726af56b23ff49ca0019020933c4ab Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Fri, 4 Mar 2022 01:41:49 +0530 Subject: [PATCH] Release/v0.7.0 (#814) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 * refactor: ✨ conditionally compute image Signed-off-by: Prashant Shahi * fix: 🩹 add signoz namespace Signed-off-by: Prashant Shahi * chore: 🔨 fix namespace template in scripts Signed-off-by: Prashant Shahi * docs(hotrod): 📝 Add README for hotrod k8s Signed-off-by: Prashant Shahi Co-authored-by: Ankit Nayan * chore(release): 📌 pin SigNoz and OtelCollector versions Signed-off-by: Prashant Shahi Co-authored-by: Pranshu Chittora Co-authored-by: Palash gupta Co-authored-by: makeavish Co-authored-by: Ankit Nayan --- .../clickhouse-setup/docker-compose.arm.yaml | 12 +- .../clickhouse-setup/docker-compose.yaml | 12 +- .../otel-collector-config.yaml | 16 +- .../otel-collector-metrics-config.yaml | 16 +- frontend/package.json | 4 + frontend/src/AppRoutes/pageComponents.ts | 11 +- frontend/src/AppRoutes/routes.ts | 10 +- frontend/src/api/trace/getSpansAggregate.ts | 1 + frontend/src/api/trace/getTraceItem.ts | 27 + frontend/src/components/NotFound/styles.ts | 1 - frontend/src/constants/routes.ts | 2 +- frontend/src/container/AppLayout/index.tsx | 2 +- frontend/src/container/AppLayout/styles.ts | 2 + .../container/GantChart/SpanLength/index.tsx | 29 ++ .../container/GantChart/SpanLength/styles.ts | 57 +++ .../container/GantChart/SpanName/index.tsx | 30 ++ .../container/GantChart/SpanName/styles.ts | 41 ++ .../src/container/GantChart/Trace/index.tsx | 186 +++++++ .../src/container/GantChart/Trace/styles.ts | 77 +++ frontend/src/container/GantChart/index.tsx | 87 ++++ frontend/src/container/GantChart/styles.ts | 47 ++ frontend/src/container/GantChart/utils.ts | 165 ++++++ .../GridGraphLayout/Graph/FullView/index.tsx | 7 +- frontend/src/container/Header/index.tsx | 2 +- frontend/src/container/SideNav/index.tsx | 13 +- frontend/src/container/SideNav/styles.ts | 16 +- frontend/src/container/Timeline/index.tsx | 105 ++++ .../src/container/Timeline/style.module.css | 8 + frontend/src/container/Timeline/types.ts | 4 + frontend/src/container/Timeline/utils.ts | 83 ++++ .../Trace/Search/AllTags/Tag/index.tsx | 2 +- frontend/src/container/Trace/Search/util.ts | 18 +- .../Trace/TraceGraphFilter/config.ts | 2 +- .../src/container/Trace/TraceTable/index.tsx | 64 ++- .../SelectedSpanDetails/ErrorTag.tsx | 94 ++++ .../TraceDetail/SelectedSpanDetails/index.tsx | 75 +++ .../TraceDetail/SelectedSpanDetails/styles.ts | 46 ++ .../TraceDetail/TraceGraph.module.css | 3 + frontend/src/container/TraceDetail/index.tsx | 200 ++++++++ frontend/src/container/TraceDetail/utils.ts | 57 +++ .../__tests__/TraceFlameGraph.test.tsx | 9 + .../TraceFlameGraph.test.tsx.snap | 3 + .../src/container/TraceFlameGraph/index.tsx | 183 +++++++ .../src/container/TraceFlameGraph/styles.ts | 38 ++ frontend/src/hooks/useThemeMode.ts | 15 + frontend/src/hooks/useUrlQuery.ts | 10 + frontend/src/lib/__tests__/getStep.test.ts | 67 +++ frontend/src/lib/getRandomColor.ts | 23 +- frontend/src/lib/getStep.ts | 43 ++ .../src/modules/Traces/FilterStateDisplay.tsx | 129 ----- .../modules/Traces/SelectedSpanDetails.tsx | 109 ---- .../TraceGanttChart/TraceGanttChart.css | 15 - .../TraceGanttChart/TraceGanttChart.tsx | 389 --------------- .../TraceGanttChart/TraceGanttChartHelpers.ts | 40 -- .../modules/Traces/TraceGanttChart/index.tsx | 1 - frontend/src/modules/Traces/TraceGraph.css | 59 --- frontend/src/modules/Traces/TraceGraph.tsx | 216 -------- .../src/modules/Traces/TraceGraphColumn.tsx | 76 --- frontend/src/modules/Traces/TraceGraphDef.tsx | 5 - frontend/src/modules/Traces/styles.ts | 16 - frontend/src/pages/Trace/styles.ts | 1 - frontend/src/pages/TraceDetail/constants.ts | 1 + frontend/src/pages/TraceDetail/index.tsx | 32 ++ frontend/src/store/actions/index.ts | 2 - .../store/actions/metrics/getInitialData.ts | 3 +- .../actions/trace/getInitialSpansAggregate.ts | 7 +- frontend/src/store/actions/traceFilters.ts | 38 -- frontend/src/store/actions/traces.ts | 4 + frontend/src/store/actions/types.ts | 10 +- frontend/src/store/reducers/index.ts | 4 - frontend/src/store/reducers/traceFilters.ts | 29 -- frontend/src/store/reducers/traces.ts | 33 -- .../src/types/api/trace/getSpanAggregate.ts | 1 + frontend/src/types/api/trace/getTraceItem.ts | 50 ++ frontend/src/types/global.d.ts | 17 + frontend/src/typings/react-app-env.ts | 1 - frontend/src/typings/react-graph-vis.ts | 39 -- frontend/src/utils/getSpanTreeMetadata.ts | 42 ++ frontend/src/utils/spanToTree.ts | 35 +- frontend/src/utils/toFixed.ts | 6 + frontend/tsconfig.json | 3 +- frontend/webpack.config.js | 13 +- frontend/webpack.config.prod.js | 13 +- frontend/yarn.lock | 470 +++++++++++++++++- pkg/query-service/model/response.go | 11 +- pkg/query-service/telemetry/telemetry.go | 5 +- sample-apps/hotrod/README.md | 27 + sample-apps/hotrod/hotrod-delete.sh | 20 + sample-apps/hotrod/hotrod-install.sh | 52 ++ sample-apps/hotrod/hotrod-template.yaml | 223 +++++++++ 90 files changed, 2905 insertions(+), 1367 deletions(-) create mode 100644 frontend/src/api/trace/getTraceItem.ts create mode 100644 frontend/src/container/GantChart/SpanLength/index.tsx create mode 100644 frontend/src/container/GantChart/SpanLength/styles.ts create mode 100644 frontend/src/container/GantChart/SpanName/index.tsx create mode 100644 frontend/src/container/GantChart/SpanName/styles.ts create mode 100644 frontend/src/container/GantChart/Trace/index.tsx create mode 100644 frontend/src/container/GantChart/Trace/styles.ts create mode 100644 frontend/src/container/GantChart/index.tsx create mode 100644 frontend/src/container/GantChart/styles.ts create mode 100644 frontend/src/container/GantChart/utils.ts create mode 100644 frontend/src/container/Timeline/index.tsx create mode 100644 frontend/src/container/Timeline/style.module.css create mode 100644 frontend/src/container/Timeline/types.ts create mode 100644 frontend/src/container/Timeline/utils.ts create mode 100644 frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx create mode 100644 frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx create mode 100644 frontend/src/container/TraceDetail/SelectedSpanDetails/styles.ts create mode 100644 frontend/src/container/TraceDetail/TraceGraph.module.css create mode 100644 frontend/src/container/TraceDetail/index.tsx create mode 100644 frontend/src/container/TraceDetail/utils.ts create mode 100644 frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx create mode 100644 frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap create mode 100644 frontend/src/container/TraceFlameGraph/index.tsx create mode 100644 frontend/src/container/TraceFlameGraph/styles.ts create mode 100644 frontend/src/hooks/useThemeMode.ts create mode 100644 frontend/src/hooks/useUrlQuery.ts create mode 100644 frontend/src/lib/__tests__/getStep.test.ts create mode 100644 frontend/src/lib/getStep.ts delete mode 100644 frontend/src/modules/Traces/FilterStateDisplay.tsx delete mode 100644 frontend/src/modules/Traces/SelectedSpanDetails.tsx delete mode 100644 frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.css delete mode 100644 frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.tsx delete mode 100644 frontend/src/modules/Traces/TraceGanttChart/TraceGanttChartHelpers.ts delete mode 100644 frontend/src/modules/Traces/TraceGanttChart/index.tsx delete mode 100644 frontend/src/modules/Traces/TraceGraph.css delete mode 100644 frontend/src/modules/Traces/TraceGraph.tsx delete mode 100644 frontend/src/modules/Traces/TraceGraphColumn.tsx delete mode 100644 frontend/src/modules/Traces/TraceGraphDef.tsx delete mode 100644 frontend/src/modules/Traces/styles.ts create mode 100644 frontend/src/pages/TraceDetail/constants.ts create mode 100644 frontend/src/pages/TraceDetail/index.tsx delete mode 100644 frontend/src/store/actions/traceFilters.ts delete mode 100644 frontend/src/store/reducers/traceFilters.ts delete mode 100644 frontend/src/store/reducers/traces.ts create mode 100644 frontend/src/types/api/trace/getTraceItem.ts create mode 100644 frontend/src/types/global.d.ts delete mode 100644 frontend/src/typings/react-app-env.ts delete mode 100644 frontend/src/typings/react-graph-vis.ts create mode 100644 frontend/src/utils/getSpanTreeMetadata.ts create mode 100644 frontend/src/utils/toFixed.ts create mode 100644 sample-apps/hotrod/README.md create mode 100755 sample-apps/hotrod/hotrod-delete.sh create mode 100755 sample-apps/hotrod/hotrod-install.sh create mode 100644 sample-apps/hotrod/hotrod-template.yaml diff --git a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml index f666105b32..66dbcec949 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml @@ -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: diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 3c5dbbc64d..96b27e6ca8 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -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: diff --git a/deploy/docker/clickhouse-setup/otel-collector-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-config.yaml index eeaf7221d7..a4a2641daa 100644 --- a/deploy/docker/clickhouse-setup/otel-collector-config.yaml +++ b/deploy/docker/clickhouse-setup/otel-collector-config.yaml @@ -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 diff --git a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml index c1c046f504..3af039268c 100644 --- a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml +++ b/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml @@ -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 diff --git a/frontend/package.json b/frontend/package.json index 4eaf1f5fd8..576a5260f7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" } diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index feb99a7c24..eb7430eb3c 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -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( diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index d508828dea..fd598b1b2c 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -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, diff --git a/frontend/src/api/trace/getSpansAggregate.ts b/frontend/src/api/trace/getSpansAggregate.ts index 1f63f034e4..b630e8ce30 100644 --- a/frontend/src/api/trace/getSpansAggregate.ts +++ b/frontend/src/api/trace/getSpansAggregate.ts @@ -15,6 +15,7 @@ const getSpanAggregate = async ( end: String(props.end), limit: props.limit, offset: props.offset, + order: props.order, }; const exclude: TraceFilterEnum[] = []; diff --git a/frontend/src/api/trace/getTraceItem.ts b/frontend/src/api/trace/getTraceItem.ts new file mode 100644 index 0000000000..1d00340852 --- /dev/null +++ b/frontend/src/api/trace/getTraceItem.ts @@ -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 | ErrorResponse> => { + try { + const response = await axios.request({ + 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; diff --git a/frontend/src/components/NotFound/styles.ts b/frontend/src/components/NotFound/styles.ts index 731ab31c2a..812fba7f6c 100644 --- a/frontend/src/components/NotFound/styles.ts +++ b/frontend/src/components/NotFound/styles.ts @@ -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; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index ed685729dd..acd95003ea 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -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', diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 407d57e3e8..b5a47f6c5c 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -37,7 +37,7 @@ const AppLayout: React.FC = ({ children }) => { {!isSignUpPage && } {children} -
{`SigNoz Inc. © ${currentYear}`}
+ {/*
{`SigNoz Inc. © ${currentYear}`}
*/} ); diff --git a/frontend/src/container/AppLayout/styles.ts b/frontend/src/container/AppLayout/styles.ts index 8c7098e425..3bbd06db34 100644 --- a/frontend/src/container/AppLayout/styles.ts +++ b/frontend/src/container/AppLayout/styles.ts @@ -12,6 +12,8 @@ export const Layout = styled(LayoutComponent)` export const Content = styled(LayoutComponent.Content)` &&& { margin: 0 1rem; + display: flex; + flex-direction: column; } `; diff --git a/frontend/src/container/GantChart/SpanLength/index.tsx b/frontend/src/container/GantChart/SpanLength/index.tsx new file mode 100644 index 0000000000..38d6d46b4e --- /dev/null +++ b/frontend/src/container/GantChart/SpanLength/index.tsx @@ -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 ( + + + + {`${toFixed(resolveTimeFromInterval(props.inMsCount, intervalUnit), 2)} ${intervalUnit.name}`} + + ); +}; + +export default SpanLength; diff --git a/frontend/src/container/GantChart/SpanLength/styles.ts b/frontend/src/container/GantChart/SpanLength/styles.ts new file mode 100644 index 0000000000..9ee22feab3 --- /dev/null +++ b/frontend/src/container/GantChart/SpanLength/styles.ts @@ -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` + width: ${({ leftOffset }) => `${leftOffset}%`}; + height: 0px; + border-bottom: 0.1px solid + ${({ isDarkMode }) => (isDarkMode ? '#303030' : '#c0c0c0')}; + top: 50%; + position: absolute; +`; +export const SpanBorder = styled.div` + 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)>` + &&& { + left: ${({ leftOffset }) => `${leftOffset}%`}; + top: 65%; + position: absolute; + color: ${({ isDarkMode }) => (isDarkMode ? '##ACACAC' : '#666')}; + font-size: 0.75rem; + } +`; diff --git a/frontend/src/container/GantChart/SpanName/index.tsx b/frontend/src/container/GantChart/SpanName/index.tsx new file mode 100644 index 0000000000..e0614b4600 --- /dev/null +++ b/frontend/src/container/GantChart/SpanName/index.tsx @@ -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 ( + + + {name} + {serviceName} + + + ); +}; + +interface SpanNameComponent { + name: string; + serviceName: string; +} + +export default SpanNameComponent; diff --git a/frontend/src/container/GantChart/SpanName/styles.ts b/frontend/src/container/GantChart/SpanName/styles.ts new file mode 100644 index 0000000000..b037349d7e --- /dev/null +++ b/frontend/src/container/GantChart/SpanName/styles.ts @@ -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; +`; diff --git a/frontend/src/container/GantChart/Trace/index.tsx b/frontend/src/container/GantChart/Trace/index.tsx new file mode 100644 index 0000000000..341500e6fa --- /dev/null +++ b/frontend/src/container/GantChart/Trace/index.tsx @@ -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(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(0); + + const ref = useRef(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 ( + <> + + + + + + + + {totalSpans !== 1 && ( + { + e.stopPropagation() + setOpen((state) => !state); + }} + > + {totalSpans} + + {isOpen ? : } + + + )} + + + + + + + + + + + + {isOpen && ( + <> + {props.children.map((child) => ( + + ))} + + )} + + + ); +}; + +interface ITraceGlobal { + globalSpread: ITraceMetaData['spread']; + globalStart: ITraceMetaData['globalStart']; +} + +interface TraceProps extends pushDStree, ITraceGlobal { + activeHoverId: string; + setActiveHoverId: React.Dispatch>; + setActiveSelectedId: React.Dispatch>; + activeSelectedId: string; + level: number; + activeSpanPath: string[]; + isExpandAll: boolean; + intervalUnit: IIntervalUnit; +} + +export default Trace; diff --git a/frontend/src/container/GantChart/Trace/styles.ts b/frontend/src/container/GantChart/Trace/styles.ts new file mode 100644 index 0000000000..b500db6120 --- /dev/null +++ b/frontend/src/container/GantChart/Trace/styles.ts @@ -0,0 +1,77 @@ +import styled, { css } from 'styled-components'; + +interface Props { + isOnlyChild: boolean; +} + +export const Wrapper = styled.ul` + 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` + 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; +`; diff --git a/frontend/src/container/GantChart/index.tsx b/frontend/src/container/GantChart/index.tsx new file mode 100644 index 0000000000..fa8b37d15a --- /dev/null +++ b/frontend/src/container/GantChart/index.tsx @@ -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(false); + const [activeSpanPath, setActiveSpanPath] = useState([]); + + useEffect(() => { + setActiveSpanPath(getSpanPath(data, spanId)) + }, [spanId]); + + useEffect(() => { + setActiveSpanPath(getSpanPath(data, activeSelectedId)) + }, [activeSelectedId]); + + const handleCollapse = () => { + setIsExpandAll((prev) => !prev); + }; + return ( + <> + + + + {isExpandAll ? : } + + + + + + + + ); +}; + +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>; + setActiveSelectedId: React.Dispatch>; + spanId: string; + intervalUnit: IIntervalUnit +} + +export default GanttChart; diff --git a/frontend/src/container/GantChart/styles.ts b/frontend/src/container/GantChart/styles.ts new file mode 100644 index 0000000000..cefa618526 --- /dev/null +++ b/frontend/src/container/GantChart/styles.ts @@ -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; +`; diff --git a/frontend/src/container/GantChart/utils.ts b/frontend/src/container/GantChart/utils.ts new file mode 100644 index 0000000000..8ee9411e05 --- /dev/null +++ b/frontend/src/container/GantChart/utils.ts @@ -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 => { + 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; +}; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 61aae23128..5da66f3394 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -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, diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx index 8884b2231b..a9636c0b9f 100644 --- a/frontend/src/container/Header/index.tsx +++ b/frontend/src/container/Header/index.tsx @@ -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 => { diff --git a/frontend/src/container/SideNav/index.tsx b/frontend/src/container/SideNav/index.tsx index 6e203d4ab2..93c618205b 100644 --- a/frontend/src/container/SideNav/index.tsx +++ b/frontend/src/container/SideNav/index.tsx @@ -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 => { {name} ))} - - }> + }> Support - + diff --git a/frontend/src/container/SideNav/styles.ts b/frontend/src/container/SideNav/styles.ts index 2b75d0dc5a..534d02183a 100644 --- a/frontend/src/container/SideNav/styles.ts +++ b/frontend/src/container/SideNav/styles.ts @@ -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` + position: fixed; + bottom: 48px; + background: #262626; + width: ${({ collapsed }) => (!collapsed ? '200px' : '80px')}; + &&& { li { ${({ collapsed }) => diff --git a/frontend/src/container/Timeline/index.tsx b/frontend/src/container/Timeline/index.tsx new file mode 100644 index 0000000000..c729ac15f0 --- /dev/null +++ b/frontend/src/container/Timeline/index.tsx @@ -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(); + const { isDarkMode } = useThemeMode(); + + const Timeline_Height = 22; + const Timeline_H_Spacing = 0; + + const [intervals, setIntervals] = useState(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 ( +
+ + + {intervals && + intervals.map((interval, index) => ( + + + {interval.label} + + + + ))} + +
+ ); +}; + +export default Timeline; diff --git a/frontend/src/container/Timeline/style.module.css b/frontend/src/container/Timeline/style.module.css new file mode 100644 index 0000000000..2ddc6e497f --- /dev/null +++ b/frontend/src/container/Timeline/style.module.css @@ -0,0 +1,8 @@ +.svg-container { + overflow: visible; + position: absolute; +} +.timeline-tick { + text-anchor: middle; + font-size: 0.6rem; +} diff --git a/frontend/src/container/Timeline/types.ts b/frontend/src/container/Timeline/types.ts new file mode 100644 index 0000000000..b4cc4cee9a --- /dev/null +++ b/frontend/src/container/Timeline/types.ts @@ -0,0 +1,4 @@ +export interface Interval { + label: string; + percentage: number; +} diff --git a/frontend/src/container/Timeline/utils.ts b/frontend/src/container/Timeline/utils.ts new file mode 100644 index 0000000000..e7e6a79a01 --- /dev/null +++ b/frontend/src/container/Timeline/utils.ts @@ -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; +}; diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx index a377de487f..f8072ab895 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx @@ -75,7 +75,7 @@ const SingleTags = (props: AllTagsProps): JSX.Element => { value={AllMenu.find((e) => e.key === selectedOperator)?.value || ''} > {AllMenu.map((e) => ( - ))} diff --git a/frontend/src/container/Trace/Search/util.ts b/frontend/src/container/Trace/Search/util.ts index d74a133229..91e235b4a1 100644 --- a/frontend/src/container/Trace/Search/util.ts +++ b/frontend/src/container/Trace/Search/util.ts @@ -13,14 +13,14 @@ export const parseQueryToTags = (query: string): PayloadProps => { 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 => { 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 => { 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 '); diff --git a/frontend/src/container/Trace/TraceGraphFilter/config.ts b/frontend/src/container/Trace/TraceGraphFilter/config.ts index 5d7a942b35..0c97ca78d5 100644 --- a/frontend/src/container/Trace/TraceGraphFilter/config.ts +++ b/frontend/src/container/Trace/TraceGraphFilter/config.ts @@ -77,7 +77,7 @@ export const functions: Dropdown[] = [ key: 'p50', }, { - displayValue: '90th percentile(duration in ns', + displayValue: '90th percentile(duration in ns)', key: 'p90', }, { diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index 919f680778..9a1155a5c7 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -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
{day.format('DD/MM/YYYY hh:mm:ss A')}
; }, - 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 ( -
- {`${dayjs - .duration({ milliseconds: value / 1000000 }) - .asMilliseconds()} ms`} -
- ); - }, + render: (value: TableType['durationNano']): JSX.Element => ( +
+ {`${dayjs + .duration({ milliseconds: value / 1000000 }) + .asMilliseconds()} ms`} +
+ ), }, { title: 'Method', dataIndex: 'httpMethod', key: 'httpMethod', - render: (value: TableType['httpMethod']) => { + render: (value: TableType['httpMethod']): JSX.Element => { if (value.length === 0) { return
-
; } @@ -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
-
; } @@ -98,7 +91,13 @@ const TraceTable = ({ getSpansAggregate }: TraceProps) => { }, ]; - const onChangeHandler: TableProps['onChange'] = (props) => { + const onChangeHandler: TableProps['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, }); }, })} diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx new file mode 100644 index 0000000000..ebe2ea2559 --- /dev/null +++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx @@ -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 ( + + + {attributes.map((event) => { + const value = attributeMap[event]; + const isEllipsed = value.length > 24; + + return ( + <> + {event} + + {value} +
+ {isEllipsed && ( + + )} +
+ + + + ); + })} +
+
+ ); + })} + onToggleHandler(false)} + title="Log Message" + visible={isOpen} + destroyOnClose + footer={[]} + > + {text.text} + + {text.subText} + + + + ); +}; + +interface ErrorTagProps { + event: ITraceTree['event']; +} + +export default ErrorTag; diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx new file mode 100644 index 0000000000..35ed013d9c --- /dev/null +++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx @@ -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 ( + + + Details for selected Span + + Service + {serviceName} + + + Operation + {name} + + + + + {tags.length !== 0 ? ( + tags.map((tags) => { + return ( + + {tags.value && ( + <> + {tags.key} + + {tags.key === 'error' ? 'true' : tags.value} + + + )} + + ); + }) + ) : ( + No tags in selected span + )} + + + {tree.event && Object.keys(tree.event).length !== 0 ? ( + + ) : ( + No events data in selected span + )} + + + + ); +}; + +interface SelectedSpanDetailsProps { + tree?: ITraceTree; +} + +export default SelectedSpanDetails; diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/styles.ts b/frontend/src/container/TraceDetail/SelectedSpanDetails/styles.ts new file mode 100644 index 0000000000..f467a87b35 --- /dev/null +++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/styles.ts @@ -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)` + &&& { + 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; +`; diff --git a/frontend/src/container/TraceDetail/TraceGraph.module.css b/frontend/src/container/TraceDetail/TraceGraph.module.css new file mode 100644 index 0000000000..9127050c97 --- /dev/null +++ b/frontend/src/container/TraceDetail/TraceGraph.module.css @@ -0,0 +1,3 @@ +.trace-detail-content-spacing { + margin-right: 1rem; +} diff --git a/frontend/src/container/TraceDetail/index.tsx b/frontend/src/container/TraceDetail/index.tsx new file mode 100644 index 0000000000..99d693fc85 --- /dev/null +++ b/frontend/src/container/TraceDetail/index.tsx @@ -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(urlQuery.get('spanId')); + + const [intervalUnit, setIntervalUnit] = useState(INTERVAL_UNITS[0]); + const [searchSpanString, setSearchSpanString] = useState(''); + const [activeHoverId, setActiveHoverId] = useState(''); + const [activeSelectedId, setActiveSelectedId] = useState(spanId || ''); + + const [treeData, setTreeData] = useState( + spanToTreeUtil(response[0].events), + ); + + const { treeData: tree, ...traceMetaData } = useMemo(() => { + return getSpanTreeMetadata(getSortedData(treeData), spanServiceColors); + }, [treeData]); + + const [globalTraceMetadata, _setGlobalTraceMetadata] = useState({ + ...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 ( + + + + + + Trace Details + + + {traceMetaData.totalSpans} Span + + + + + + + + + {dayjs(traceMetaData.globalStart / 1e6).format('hh:mm:ssa MM/DD')} + + + + + + + + + {/* */} + + + + + + + + +
+ +
+ + + + + + + +
+ ); +}; + +interface TraceDetailProps { + response: PayloadProps; +} + +export default TraceDetail; diff --git a/frontend/src/container/TraceDetail/utils.ts b/frontend/src/container/TraceDetail/utils.ts new file mode 100644 index 0000000000..c92e6b37bb --- /dev/null +++ b/frontend/src/container/TraceDetail/utils.ts @@ -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; +}; diff --git a/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx b/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx new file mode 100644 index 0000000000..74c17c3139 --- /dev/null +++ b/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx @@ -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(); + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap b/frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap new file mode 100644 index 0000000000..ec5ff7b75b --- /dev/null +++ b/frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`loads and displays greeting 1`] = ``; diff --git a/frontend/src/container/TraceFlameGraph/index.tsx b/frontend/src/container/TraceFlameGraph/index.tsx new file mode 100644 index 0000000000..511becaf39 --- /dev/null +++ b/frontend/src/container/TraceFlameGraph/index.tsx @@ -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(false); + const [isLocalHover, setIsLocalHover] = useState(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 ( + <> + { + handleHover(true); + }} + onMouseLeave={() => { + handleHover(false); + }} + topOffset={topOffset} + leftOffset={leftOffset} + width={width} + spanColor={spanColor} + selected={isSelected} + zIdx={isSelected ? 1 : 0} + > + + ); +}; + +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 ( + <> + + {spanData.children.map((childData) => ( + + ))} + + ); + }; + return ( + <> + + + + + ); +}; + +export default TraceFlameGraph; diff --git a/frontend/src/container/TraceFlameGraph/styles.ts b/frontend/src/container/TraceFlameGraph/styles.ts new file mode 100644 index 0000000000..ad262421cd --- /dev/null +++ b/frontend/src/container/TraceFlameGraph/styles.ts @@ -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; +`; diff --git a/frontend/src/hooks/useThemeMode.ts b/frontend/src/hooks/useThemeMode.ts new file mode 100644 index 0000000000..496ea82904 --- /dev/null +++ b/frontend/src/hooks/useThemeMode.ts @@ -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((state) => state.app); + + return { isDarkMode }; +}; + +export default useThemeMode; diff --git a/frontend/src/hooks/useUrlQuery.ts b/frontend/src/hooks/useUrlQuery.ts new file mode 100644 index 0000000000..6f20744a2d --- /dev/null +++ b/frontend/src/hooks/useUrlQuery.ts @@ -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; diff --git a/frontend/src/lib/__tests__/getStep.test.ts b/frontend/src/lib/__tests__/getStep.test.ts new file mode 100644 index 0000000000..638c21cf4e --- /dev/null +++ b/frontend/src/lib/__tests__/getStep.test.ts @@ -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); + }); +}); diff --git a/frontend/src/lib/getRandomColor.ts b/frontend/src/lib/getRandomColor.ts index 49361caae8..5eaaa2a724 100644 --- a/frontend/src/lib/getRandomColor.ts +++ b/frontend/src/lib/getRandomColor.ts @@ -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; diff --git a/frontend/src/lib/getStep.ts b/frontend/src/lib/getStep.ts new file mode 100644 index 0000000000..2458782d27 --- /dev/null +++ b/frontend/src/lib/getStep.ts @@ -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; diff --git a/frontend/src/modules/Traces/FilterStateDisplay.tsx b/frontend/src/modules/Traces/FilterStateDisplay.tsx deleted file mode 100644 index 37b747a11b..0000000000 --- a/frontend/src/modules/Traces/FilterStateDisplay.tsx +++ /dev/null @@ -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 ( - - {props.traceFilters.service === '' || - props.traceFilters.operation === undefined ? null : ( - { - handleCloseTag('service'); - }} - > - service:{props.traceFilters.service} - - )} - {props.traceFilters.operation === '' || - props.traceFilters.operation === undefined ? null : ( - { - handleCloseTag('operation'); - }} - > - operation:{props.traceFilters.operation} - - )} - {props.traceFilters.latency === undefined || - props.traceFilters.latency?.min === '' ? null : ( - { - handleCloseTag('minLatency'); - }} - > - minLatency: - {(parseInt(traceFilters?.latency?.min || '0') / 1000000).toString()}ms - - )} - {props.traceFilters.latency === undefined || - props.traceFilters.latency?.max === '' ? null : ( - { - handleCloseTag('maxLatency'); - }} - > - maxLatency: - {(parseInt(traceFilters?.latency?.max || '0') / 1000000).toString()}ms - - )} - {props.traceFilters.tags === undefined - ? null - : props.traceFilters.tags.map((item) => ( - { - handleCloseTagElement(item); - }} - > - {item.key} {item.operator} {item.value} - - ))} - - ); -}; - -const mapStateToProps = (state: AppState): { traceFilters: TraceFilters } => { - return { traceFilters: state.traceFilters }; -}; - -export const FilterStateDisplay = connect(mapStateToProps, { - updateTraceFilters: updateTraceFilters, -})(_FilterStateDisplay); diff --git a/frontend/src/modules/Traces/SelectedSpanDetails.tsx b/frontend/src/modules/Traces/SelectedSpanDetails.tsx deleted file mode 100644 index 427066081e..0000000000 --- a/frontend/src/modules/Traces/SelectedSpanDetails.tsx +++ /dev/null @@ -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 ( - - - Details for selected Span - - Service - {service} - - - Operation - {operation} - - - - - {spanTags && - spanTags.map((tags) => { - return ( - <> - {tags.value && ( - <> - {tags.key} - - {tags.key === 'error' ? 'true' : tags.value} - - - )} - - ); - })} - - - {spanTags && - spanTags - .filter((tags) => tags.key === 'error') - .map((error) => ( - <> - {error.key} - true - - ))} - - - - ); -}; - -export default SelectedSpanDetails; diff --git a/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.css b/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.css deleted file mode 100644 index 98bae47c4a..0000000000 --- a/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.css +++ /dev/null @@ -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; -} diff --git a/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.tsx b/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.tsx deleted file mode 100644 index 032727766b..0000000000 --- a/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.tsx +++ /dev/null @@ -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([]); - const [clickedSpanData, setClickedSpanData] = useState(clickedSpan); - const [defaultExpandedRows, setDefaultExpandedRows] = useState([]); - const [sortedTreeData, setSortedTreeData] = useState(treeData); - const [isReset, setIsReset] = useState(false); - const [tabsContainerWidth, setTabsContainerWidth] = useState(0); - const tableRef = useRef(''); - const tabsContainer = document.querySelector( - '#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: ( - - - - - - ), - 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 ( - <> -
{duration}ms
- - - ); - }, - }, - ]; - - 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( - `[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' && ( - <> - - - - - - - Focus on selected path - - Reset Focus - - - - { - 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; diff --git a/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChartHelpers.ts b/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChartHelpers.ts deleted file mode 100644 index 55e7e5aee6..0000000000 --- a/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChartHelpers.ts +++ /dev/null @@ -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; diff --git a/frontend/src/modules/Traces/TraceGanttChart/index.tsx b/frontend/src/modules/Traces/TraceGanttChart/index.tsx deleted file mode 100644 index cce8f15870..0000000000 --- a/frontend/src/modules/Traces/TraceGanttChart/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './TraceGanttChart'; diff --git a/frontend/src/modules/Traces/TraceGraph.css b/frontend/src/modules/Traces/TraceGraph.css deleted file mode 100644 index 99648f61f2..0000000000 --- a/frontend/src/modules/Traces/TraceGraph.css +++ /dev/null @@ -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; -} diff --git a/frontend/src/modules/Traces/TraceGraph.tsx b/frontend/src/modules/Traces/TraceGraph.tsx deleted file mode 100644 index 2cd6e59c1d..0000000000 --- a/frontend/src/modules/Traces/TraceGraph.tsx +++ /dev/null @@ -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([]); - const [selectedSpan, setSelectedSpan] = useState({}); - const [clickedSpan, setClickedSpan] = useState(null); - const [resetZoom, setResetZoom] = useState(false); - const [sortedTreeData, setSortedTreeData] = useState([]); - - 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 + '
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 ( - -
- - -
-
- Trace Graph component ID is {id}{' '} -
-
-
-
- - - - - -
- - - - - - - - ); -}; - -const mapStateToProps = ( - state: AppState, -): { traceItem: spansWSameTraceIDResponse } => { - return { traceItem: state.traceItem }; -}; - -export const TraceGraph = connect(mapStateToProps, { - fetchTraceItem: fetchTraceItem, -})(_TraceGraph); diff --git a/frontend/src/modules/Traces/TraceGraphColumn.tsx b/frontend/src/modules/Traces/TraceGraphColumn.tsx deleted file mode 100644 index c32524580c..0000000000 --- a/frontend/src/modules/Traces/TraceGraphColumn.tsx +++ /dev/null @@ -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 ( -
-
; - - ); -}; - -const mapStateToProps = (state: AppState): { traces: traceResponseNew } => { - return { traces: state.traces }; -}; - -export const TraceGraphColumn = connect(mapStateToProps)(_TraceGraphColumn); diff --git a/frontend/src/modules/Traces/TraceGraphDef.tsx b/frontend/src/modules/Traces/TraceGraphDef.tsx deleted file mode 100644 index 13658a60ec..0000000000 --- a/frontend/src/modules/Traces/TraceGraphDef.tsx +++ /dev/null @@ -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 diff --git a/frontend/src/modules/Traces/styles.ts b/frontend/src/modules/Traces/styles.ts deleted file mode 100644 index a16e487b3d..0000000000 --- a/frontend/src/modules/Traces/styles.ts +++ /dev/null @@ -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; -`; diff --git a/frontend/src/pages/Trace/styles.ts b/frontend/src/pages/Trace/styles.ts index f124268258..d7cf4c0869 100644 --- a/frontend/src/pages/Trace/styles.ts +++ b/frontend/src/pages/Trace/styles.ts @@ -5,7 +5,6 @@ export const Container = styled.div` display: flex; flex: 1; min-height: 80vh; - margin-top: 1rem; `; diff --git a/frontend/src/pages/TraceDetail/constants.ts b/frontend/src/pages/TraceDetail/constants.ts new file mode 100644 index 0000000000..0253cbeff0 --- /dev/null +++ b/frontend/src/pages/TraceDetail/constants.ts @@ -0,0 +1 @@ +export const SPAN_DETAILS_LEFT_COL_WIDTH = 350; diff --git a/frontend/src/pages/TraceDetail/index.tsx b/frontend/src/pages/TraceDetail/index.tsx new file mode 100644 index 0000000000..bf29a5244b --- /dev/null +++ b/frontend/src/pages/TraceDetail/index.tsx @@ -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(); + + const traceDetailResponse = useFetch(getTraceItem, { + id, + }); + + if (traceDetailResponse.error) { + return ( + + {traceDetailResponse.errorMessage || 'Something went wrong'} + + ); + } + + if (traceDetailResponse.loading || traceDetailResponse.payload === undefined) { + return ; + } + + return ; +}; + +export default TraceDetail; diff --git a/frontend/src/store/actions/index.ts b/frontend/src/store/actions/index.ts index 22d4fd9178..6ee8a31184 100644 --- a/frontend/src/store/actions/index.ts +++ b/frontend/src/store/actions/index.ts @@ -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'; diff --git a/frontend/src/store/actions/metrics/getInitialData.ts b/frontend/src/store/actions/metrics/getInitialData.ts index 7c43fc0427..942cb0dbed 100644 --- a/frontend/src/store/actions/metrics/getInitialData.ts +++ b/frontend/src/store/actions/metrics/getInitialData.ts @@ -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, diff --git a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts index 0ae6362eea..11d061e079 100644 --- a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts +++ b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts @@ -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']; } diff --git a/frontend/src/store/actions/traceFilters.ts b/frontend/src/store/actions/traceFilters.ts deleted file mode 100644 index 35502674fd..0000000000 --- a/frontend/src/store/actions/traceFilters.ts +++ /dev/null @@ -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 diff --git a/frontend/src/store/actions/traces.ts b/frontend/src/store/actions/traces.ts index 72200fb5eb..8ac074cd2f 100644 --- a/frontend/src/store/actions/traces.ts +++ b/frontend/src/store/actions/traces.ts @@ -43,6 +43,10 @@ export interface pushDStree { startTime: number; tags: TraceTagItem[]; children: pushDStree[]; + parent: pushDStree; + serviceName: string; + serviceColour: string; + hasError: boolean; } export interface spanItem { diff --git a/frontend/src/store/actions/types.ts b/frontend/src/store/actions/types.ts index 6c2902c1b3..2aaa38f9c3 100644 --- a/frontend/src/store/actions/types.ts +++ b/frontend/src/store/actions/types.ts @@ -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; diff --git a/frontend/src/store/reducers/index.ts b/frontend/src/store/reducers/index.ts index 9c2bdecb1f..690464b215 100644 --- a/frontend/src/store/reducers/index.ts +++ b/frontend/src/store/reducers/index.ts @@ -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, diff --git a/frontend/src/store/reducers/traceFilters.ts b/frontend/src/store/reducers/traceFilters.ts deleted file mode 100644 index d234015c73..0000000000 --- a/frontend/src/store/reducers/traceFilters.ts +++ /dev/null @@ -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; diff --git a/frontend/src/store/reducers/traces.ts b/frontend/src/store/reducers/traces.ts deleted file mode 100644 index afe8436818..0000000000 --- a/frontend/src/store/reducers/traces.ts +++ /dev/null @@ -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; - } -}; diff --git a/frontend/src/types/api/trace/getSpanAggregate.ts b/frontend/src/types/api/trace/getSpanAggregate.ts index 0c2232efa9..3263621009 100644 --- a/frontend/src/types/api/trace/getSpanAggregate.ts +++ b/frontend/src/types/api/trace/getSpanAggregate.ts @@ -7,6 +7,7 @@ export interface Props { limit: number; offset: number; selectedTags: TraceReducer['selectedTags']; + order?: 'descending' | 'ascending'; isFilterExclude: TraceReducer['isFilterExclude']; } diff --git a/frontend/src/types/api/trace/getTraceItem.ts b/frontend/src/types/api/trace/getTraceItem.ts new file mode 100644 index 0000000000..fc05448650 --- /dev/null +++ b/frontend/src/types/api/trace/getTraceItem.ts @@ -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; +} diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts new file mode 100644 index 0000000000..dd674b33e6 --- /dev/null +++ b/frontend/src/types/global.d.ts @@ -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; +} diff --git a/frontend/src/typings/react-app-env.ts b/frontend/src/typings/react-app-env.ts deleted file mode 100644 index 6431bc5fc6..0000000000 --- a/frontend/src/typings/react-app-env.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/frontend/src/typings/react-graph-vis.ts b/frontend/src/typings/react-graph-vis.ts deleted file mode 100644 index bef518e573..0000000000 --- a/frontend/src/typings/react-graph-vis.ts +++ /dev/null @@ -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; - } -} diff --git a/frontend/src/utils/getSpanTreeMetadata.ts b/frontend/src/utils/getSpanTreeMetadata.ts new file mode 100644 index 0000000000..2938507603 --- /dev/null +++ b/frontend/src/utils/getSpanTreeMetadata.ts @@ -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, + }; +}; diff --git a/frontend/src/utils/spanToTree.ts b/frontend/src/utils/spanToTree.ts index e56e9854ea..0f50cf6d40 100644 --- a/frontend/src/utils/spanToTree.ts +++ b/frontend/src/utils/spanToTree.ts @@ -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 }; }; diff --git a/frontend/src/utils/toFixed.ts b/frontend/src/utils/toFixed.ts new file mode 100644 index 0000000000..6aabbb267e --- /dev/null +++ b/frontend/src/utils/toFixed.ts @@ -0,0 +1,6 @@ +export const toFixed = (input: number, fixedCount: number) => { + if (input.toString().split('.').length > 1) { + return input.toFixed(fixedCount); + } + return input; +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index b43a985abc..3ff85194f8 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -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"] diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 5b94f0f903..549f98e0d1 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -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', diff --git a/frontend/webpack.config.prod.js b/frontend/webpack.config.prod.js index 291fa84e15..7ccc97d6ba 100644 --- a/frontend/webpack.config.prod.js +++ b/frontend/webpack.config.prod.js @@ -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', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9bdfc03964..dd0fab967c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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" diff --git a/pkg/query-service/model/response.go b/pkg/query-service/model/response.go index fb5c460747..7526505d33 100644 --- a/pkg/query-service/model/response.go +++ b/pkg/query-service/model/response.go @@ -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 } diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 5de8392e0c..8a60be19da 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -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() } diff --git a/sample-apps/hotrod/README.md b/sample-apps/hotrod/README.md new file mode 100644 index 0000000000..1907c23dfc --- /dev/null +++ b/sample-apps/hotrod/README.md @@ -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 +``` diff --git a/sample-apps/hotrod/hotrod-delete.sh b/sample-apps/hotrod/hotrod-delete.sh new file mode 100755 index 0000000000..f73f89c1a6 --- /dev/null +++ b/sample-apps/hotrod/hotrod-delete.sh @@ -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 diff --git a/sample-apps/hotrod/hotrod-install.sh b/sample-apps/hotrod/hotrod-install.sh new file mode 100755 index 0000000000..f6f3845205 --- /dev/null +++ b/sample-apps/hotrod/hotrod-install.sh @@ -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 diff --git a/sample-apps/hotrod/hotrod-template.yaml b/sample-apps/hotrod/hotrod-template.yaml new file mode 100644 index 0000000000..6fdd6dd9ae --- /dev/null +++ b/sample-apps/hotrod/hotrod-template.yaml @@ -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