From eb63b6da2a726af56b23ff49ca0019020933c4ab Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Fri, 4 Mar 2022 01:41:49 +0530 Subject: [PATCH 001/154] 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 From 91bdb77a0e06dffb724ad50bafe5d1b930ed0ea4 Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Fri, 4 Mar 2022 12:08:54 +0530 Subject: [PATCH 002/154] Revert "Release/v0.7.0 (#814)" (#820) This reverts commit eb63b6da2a726af56b23ff49ca0019020933c4ab. --- .../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, 1367 insertions(+), 2905 deletions(-) delete mode 100644 frontend/src/api/trace/getTraceItem.ts delete mode 100644 frontend/src/container/GantChart/SpanLength/index.tsx delete mode 100644 frontend/src/container/GantChart/SpanLength/styles.ts delete mode 100644 frontend/src/container/GantChart/SpanName/index.tsx delete mode 100644 frontend/src/container/GantChart/SpanName/styles.ts delete mode 100644 frontend/src/container/GantChart/Trace/index.tsx delete mode 100644 frontend/src/container/GantChart/Trace/styles.ts delete mode 100644 frontend/src/container/GantChart/index.tsx delete mode 100644 frontend/src/container/GantChart/styles.ts delete mode 100644 frontend/src/container/GantChart/utils.ts delete mode 100644 frontend/src/container/Timeline/index.tsx delete mode 100644 frontend/src/container/Timeline/style.module.css delete mode 100644 frontend/src/container/Timeline/types.ts delete mode 100644 frontend/src/container/Timeline/utils.ts delete mode 100644 frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx delete mode 100644 frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx delete mode 100644 frontend/src/container/TraceDetail/SelectedSpanDetails/styles.ts delete mode 100644 frontend/src/container/TraceDetail/TraceGraph.module.css delete mode 100644 frontend/src/container/TraceDetail/index.tsx delete mode 100644 frontend/src/container/TraceDetail/utils.ts delete mode 100644 frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx delete mode 100644 frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap delete mode 100644 frontend/src/container/TraceFlameGraph/index.tsx delete mode 100644 frontend/src/container/TraceFlameGraph/styles.ts delete mode 100644 frontend/src/hooks/useThemeMode.ts delete mode 100644 frontend/src/hooks/useUrlQuery.ts delete mode 100644 frontend/src/lib/__tests__/getStep.test.ts delete mode 100644 frontend/src/lib/getStep.ts create mode 100644 frontend/src/modules/Traces/FilterStateDisplay.tsx create mode 100644 frontend/src/modules/Traces/SelectedSpanDetails.tsx create mode 100644 frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.css create mode 100644 frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.tsx create mode 100644 frontend/src/modules/Traces/TraceGanttChart/TraceGanttChartHelpers.ts create mode 100644 frontend/src/modules/Traces/TraceGanttChart/index.tsx create mode 100644 frontend/src/modules/Traces/TraceGraph.css create mode 100644 frontend/src/modules/Traces/TraceGraph.tsx create mode 100644 frontend/src/modules/Traces/TraceGraphColumn.tsx create mode 100644 frontend/src/modules/Traces/TraceGraphDef.tsx create mode 100644 frontend/src/modules/Traces/styles.ts delete mode 100644 frontend/src/pages/TraceDetail/constants.ts delete mode 100644 frontend/src/pages/TraceDetail/index.tsx create mode 100644 frontend/src/store/actions/traceFilters.ts create mode 100644 frontend/src/store/reducers/traceFilters.ts create mode 100644 frontend/src/store/reducers/traces.ts delete mode 100644 frontend/src/types/api/trace/getTraceItem.ts delete mode 100644 frontend/src/types/global.d.ts create mode 100644 frontend/src/typings/react-app-env.ts create mode 100644 frontend/src/typings/react-graph-vis.ts delete mode 100644 frontend/src/utils/getSpanTreeMetadata.ts delete mode 100644 frontend/src/utils/toFixed.ts delete mode 100644 sample-apps/hotrod/README.md delete mode 100755 sample-apps/hotrod/hotrod-delete.sh delete mode 100755 sample-apps/hotrod/hotrod-install.sh delete 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 66dbcec949..f666105b32 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.7.0 + image: signoz/query-service:0.6.2 container_name: query-service command: ["-config=/root/config/prometheus.yml"] volumes: @@ -40,7 +40,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.7.0 + image: signoz/frontend:0.6.2 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.43.0 - command: ["--config=/etc/otel-collector-config.yaml"] + image: signoz/otelcontribcol:0.6.0 + command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"] 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.43.0 - command: ["--config=/etc/otel-collector-metrics-config.yaml"] + image: signoz/otelcontribcol:0.6.0 + command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"] 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 96b27e6ca8..3c5dbbc64d 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.7.0 + image: signoz/query-service:0.6.2 container_name: query-service command: ["-config=/root/config/prometheus.yml"] volumes: @@ -43,7 +43,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.7.0 + image: signoz/frontend:0.6.2 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.43.0 - command: ["--config=/etc/otel-collector-config.yaml"] + image: signoz/otelcontribcol:0.6.0 + command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"] 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.43.0 - command: ["--config=/etc/otel-collector-metrics-config.yaml"] + image: signoz/otelcontribcol:0.6.0 + command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"] 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 a4a2641daa..eeaf7221d7 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 3af039268c..c1c046f504 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 576a5260f7..4eaf1f5fd8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,7 +36,6 @@ "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", @@ -60,7 +59,6 @@ "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", @@ -95,7 +93,6 @@ "@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", @@ -141,7 +138,6 @@ "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 eb7430eb3c..feb99a7c24 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -18,12 +18,15 @@ export const ServiceMapPage = Loadable( ), ); -export const TraceFilter = Loadable( - () => import(/* webpackChunkName: "Trace Filter Page" */ 'pages/Trace'), +export const TraceDetailPages = Loadable( + () => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/Trace'), ); -export const TraceDetail = Loadable( - () => import(/* webpackChunkName: "TraceDetail Page" */ 'pages/TraceDetail'), +export const TraceGraphPage = Loadable( + () => + import( + /* webpackChunkName: "TraceGraphPage" */ 'modules/Traces/TraceGraphDef' + ), ); export const UsageExplorerPage = Loadable( diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index fd598b1b2c..d508828dea 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -17,8 +17,8 @@ import { ServicesTablePage, SettingsPage, SignupPage, - TraceFilter, - TraceDetail, + TraceDetailPages, + TraceGraphPage, UsageExplorerPage, } from './pageComponents'; @@ -44,9 +44,9 @@ const routes: AppRoutes[] = [ exact: true, }, { - path: ROUTES.TRACE_DETAIL, + path: ROUTES.TRACE_GRAPH, exact: true, - component: TraceDetail, + component: TraceGraphPage, }, { path: ROUTES.SETTINGS, @@ -96,7 +96,7 @@ const routes: AppRoutes[] = [ { path: ROUTES.TRACE, exact: true, - component: TraceFilter, + component: TraceDetailPages, }, { path: ROUTES.CHANNELS_NEW, diff --git a/frontend/src/api/trace/getSpansAggregate.ts b/frontend/src/api/trace/getSpansAggregate.ts index b630e8ce30..1f63f034e4 100644 --- a/frontend/src/api/trace/getSpansAggregate.ts +++ b/frontend/src/api/trace/getSpansAggregate.ts @@ -15,7 +15,6 @@ 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 deleted file mode 100644 index 1d00340852..0000000000 --- a/frontend/src/api/trace/getTraceItem.ts +++ /dev/null @@ -1,27 +0,0 @@ -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 812fba7f6c..731ab31c2a 100644 --- a/frontend/src/components/NotFound/styles.ts +++ b/frontend/src/components/NotFound/styles.ts @@ -2,6 +2,7 @@ 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 acd95003ea..ed685729dd 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_DETAIL: '/trace/:id', + TRACE_GRAPH: '/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 b5a47f6c5c..407d57e3e8 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 3bbd06db34..8c7098e425 100644 --- a/frontend/src/container/AppLayout/styles.ts +++ b/frontend/src/container/AppLayout/styles.ts @@ -12,8 +12,6 @@ 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 deleted file mode 100644 index 38d6d46b4e..0000000000 --- a/frontend/src/container/GantChart/SpanLength/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 9ee22feab3..0000000000 --- a/frontend/src/container/GantChart/SpanLength/styles.ts +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index e0614b4600..0000000000 --- a/frontend/src/container/GantChart/SpanName/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index b037349d7e..0000000000 --- a/frontend/src/container/GantChart/SpanName/styles.ts +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 341500e6fa..0000000000 --- a/frontend/src/container/GantChart/Trace/index.tsx +++ /dev/null @@ -1,186 +0,0 @@ -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 deleted file mode 100644 index b500db6120..0000000000 --- a/frontend/src/container/GantChart/Trace/styles.ts +++ /dev/null @@ -1,77 +0,0 @@ -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 deleted file mode 100644 index fa8b37d15a..0000000000 --- a/frontend/src/container/GantChart/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -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 deleted file mode 100644 index cefa618526..0000000000 --- a/frontend/src/container/GantChart/styles.ts +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 8ee9411e05..0000000000 --- a/frontend/src/container/GantChart/utils.ts +++ /dev/null @@ -1,165 +0,0 @@ -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 5da66f3394..61aae23128 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -16,7 +16,6 @@ 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'; @@ -91,11 +90,7 @@ const FullView = ({ end: queryMinMax.max.toString(), query: query.query, start: queryMinMax.min.toString(), - step: `${getStep({ - start: queryMinMax.min, - end: queryMinMax.max, - inputFormat: 's', - })}`, + step: '60', }); return { query: query.query, diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx index a9636c0b9f..8884b2231b 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_DETAIL, + ROUTES.TRACE_GRAPH, ]; const TopNav = (): JSX.Element | null => { diff --git a/frontend/src/container/SideNav/index.tsx b/frontend/src/container/SideNav/index.tsx index 93c618205b..6e203d4ab2 100644 --- a/frontend/src/container/SideNav/index.tsx +++ b/frontend/src/container/SideNav/index.tsx @@ -1,5 +1,10 @@ import { Menu, Typography } from 'antd'; -import { SlackButton, SlackMenuItemContainer, ToggleButton } from './styles'; +import { + MenuItem, + SlackButton, + SlackMenuItemContainer, + ToggleButton, +} from './styles'; import ROUTES from 'constants/routes'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; @@ -12,6 +17,7 @@ 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'; @@ -89,10 +95,11 @@ 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 534d02183a..2b75d0dc5a 100644 --- a/frontend/src/container/SideNav/styles.ts +++ b/frontend/src/container/SideNav/styles.ts @@ -20,7 +20,6 @@ interface LogoProps { } export const Sider = styled(SiderComponent)` - z-index: 999; .ant-typography { color: white; } @@ -46,12 +45,17 @@ export const SlackButton = styled(Typography)` } `; -export const SlackMenuItemContainer = styled.div` - position: fixed; - bottom: 48px; - background: #262626; - width: ${({ collapsed }) => (!collapsed ? '200px' : '80px')}; +export const MenuItem = styled(Menu.Item)` + &&& { + position: fixed; + bottom: 48px; + width: 100%; + height: 48px; + background: #262626; + } +`; +export const SlackMenuItemContainer = styled.div` &&& { li { ${({ collapsed }) => diff --git a/frontend/src/container/Timeline/index.tsx b/frontend/src/container/Timeline/index.tsx deleted file mode 100644 index c729ac15f0..0000000000 --- a/frontend/src/container/Timeline/index.tsx +++ /dev/null @@ -1,105 +0,0 @@ -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 deleted file mode 100644 index 2ddc6e497f..0000000000 --- a/frontend/src/container/Timeline/style.module.css +++ /dev/null @@ -1,8 +0,0 @@ -.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 deleted file mode 100644 index b4cc4cee9a..0000000000 --- a/frontend/src/container/Timeline/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Interval { - label: string; - percentage: number; -} diff --git a/frontend/src/container/Timeline/utils.ts b/frontend/src/container/Timeline/utils.ts deleted file mode 100644 index e7e6a79a01..0000000000 --- a/frontend/src/container/Timeline/utils.ts +++ /dev/null @@ -1,83 +0,0 @@ -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 f8072ab895..a377de487f 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 91e235b4a1..d74a133229 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,15 +40,13 @@ export const parseQueryToTags = (query: string): PayloadProps => { const removingFirstAndLastBrackets = `${filterForTags?.slice(1, -1)}`; - const noofFilters = removingFirstAndLastBrackets - .split(',') - .map((e) => e.replaceAll(/"/g, '')); + const noofFilters = removingFirstAndLastBrackets.split(','); noofFilters.forEach((e) => { const firstChar = e.charAt(0); const lastChar = e.charAt(e.length - 1); - if (firstChar === '"' && lastChar === '"') { + if (!(firstChar === '"' && lastChar === '"')) { isError = true; } }); @@ -75,9 +73,7 @@ export const parseTagsToQuery = (tags: Tags): PayloadProps => { isError = true; } - return `${Key[0]} ${Operator} (${Values.map((e) => { - return `"${e.replaceAll(/"/g, '')}"`; - }).join(',')})`; + return `${Key[0]} ${Operator} (${Values.map((e) => `"${e}"`).join(',')})`; }) .join(' AND '); diff --git a/frontend/src/container/Trace/TraceGraphFilter/config.ts b/frontend/src/container/Trace/TraceGraphFilter/config.ts index 0c97ca78d5..5d7a942b35 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 9a1155a5c7..919f680778 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -1,24 +1,26 @@ -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 Table, { ColumnsType } from 'antd/lib/table'; +import { TableProps, Tag } from 'antd'; + 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 { TraceReducer } from 'types/reducer/trace'; +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; +import history from 'lib/history'; +import ROUTES from 'constants/routes'; dayjs.extend(duration); -const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => { +const TraceTable = ({ getSpansAggregate }: TraceProps) => { const { spansAggregate, selectedFilter, @@ -39,16 +41,17 @@ const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => { title: 'Date', dataIndex: 'timestamp', key: 'timestamp', - sorter: true, - render: (value: TableType['timestamp']): JSX.Element => { + render: (value: TableType['timestamp']) => { 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', @@ -59,19 +62,22 @@ const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => { title: 'Duration', dataIndex: 'durationNano', key: 'durationNano', - render: (value: TableType['durationNano']): JSX.Element => ( -
- {`${dayjs - .duration({ milliseconds: value / 1000000 }) - .asMilliseconds()} ms`} -
- ), + sorter: (a, b) => a.durationNano - b.durationNano, + render: (value: TableType['durationNano']) => { + return ( +
+ {`${dayjs + .duration({ milliseconds: value / 1000000 }) + .asMilliseconds()} ms`} +
+ ); + }, }, { title: 'Method', dataIndex: 'httpMethod', key: 'httpMethod', - render: (value: TableType['httpMethod']): JSX.Element => { + render: (value: TableType['httpMethod']) => { if (value.length === 0) { return
-
; } @@ -82,7 +88,8 @@ const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => { title: 'Status Code', dataIndex: 'httpCode', key: 'httpCode', - render: (value: TableType['httpCode']): JSX.Element => { + sorter: (a, b) => a.httpCode.length - b.httpCode.length, + render: (value: TableType['httpCode']) => { if (value.length === 0) { return
-
; } @@ -91,13 +98,7 @@ const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => { }, ]; - const onChangeHandler: TableProps['onChange'] = ( - props, - _, - sort, - ) => { - const { order = 'ascend' } = sort; - + const onChangeHandler: TableProps['onChange'] = (props) => { if (props.current && props.pageSize) { getSpansAggregate({ maxTime: globalTime.maxTime, @@ -106,7 +107,6 @@ const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => { current: props.current, pageSize: props.pageSize, selectedTags, - order: order === 'ascend' ? 'ascending' : 'descending', }); } }; @@ -118,10 +118,12 @@ const TraceTable = ({ getSpansAggregate }: TraceProps): JSX.Element => { loading={loading || filterLoading} columns={columns} onRow={(record) => ({ - onClick: (): void => { + onClick: () => { history.push({ pathname: ROUTES.TRACE + '/' + record.traceID, - search: '?' + 'spanId=' + record.spanID, + state: { + spanId: record.spanID, + }, }); }, })} diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx deleted file mode 100644 index ebe2ea2559..0000000000 --- a/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx +++ /dev/null @@ -1,94 +0,0 @@ -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 deleted file mode 100644 index 35ed013d9c..0000000000 --- a/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -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 deleted file mode 100644 index f467a87b35..0000000000 --- a/frontend/src/container/TraceDetail/SelectedSpanDetails/styles.ts +++ /dev/null @@ -1,46 +0,0 @@ -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 deleted file mode 100644 index 9127050c97..0000000000 --- a/frontend/src/container/TraceDetail/TraceGraph.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.trace-detail-content-spacing { - margin-right: 1rem; -} diff --git a/frontend/src/container/TraceDetail/index.tsx b/frontend/src/container/TraceDetail/index.tsx deleted file mode 100644 index 99d693fc85..0000000000 --- a/frontend/src/container/TraceDetail/index.tsx +++ /dev/null @@ -1,200 +0,0 @@ -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 deleted file mode 100644 index c92e6b37bb..0000000000 --- a/frontend/src/container/TraceDetail/utils.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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 deleted file mode 100644 index 74c17c3139..0000000000 --- a/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index ec5ff7b75b..0000000000 --- a/frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap +++ /dev/null @@ -1,3 +0,0 @@ -// 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 deleted file mode 100644 index 511becaf39..0000000000 --- a/frontend/src/container/TraceFlameGraph/index.tsx +++ /dev/null @@ -1,183 +0,0 @@ -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 deleted file mode 100644 index ad262421cd..0000000000 --- a/frontend/src/container/TraceFlameGraph/styles.ts +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index 496ea82904..0000000000 --- a/frontend/src/hooks/useThemeMode.ts +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 6f20744a2d..0000000000 --- a/frontend/src/hooks/useUrlQuery.ts +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 638c21cf4e..0000000000 --- a/frontend/src/lib/__tests__/getStep.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -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 5eaaa2a724..49361caae8 100644 --- a/frontend/src/lib/getRandomColor.ts +++ b/frontend/src/lib/getRandomColor.ts @@ -1,17 +1,14 @@ -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; } @@ -21,16 +18,4 @@ 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 deleted file mode 100644 index 2458782d27..0000000000 --- a/frontend/src/lib/getStep.ts +++ /dev/null @@ -1,43 +0,0 @@ -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 new file mode 100644 index 0000000000..37b747a11b --- /dev/null +++ b/frontend/src/modules/Traces/FilterStateDisplay.tsx @@ -0,0 +1,129 @@ +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 new file mode 100644 index 0000000000..427066081e --- /dev/null +++ b/frontend/src/modules/Traces/SelectedSpanDetails.tsx @@ -0,0 +1,109 @@ +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 new file mode 100644 index 0000000000..98bae47c4a --- /dev/null +++ b/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.css @@ -0,0 +1,15 @@ +.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 new file mode 100644 index 0000000000..032727766b --- /dev/null +++ b/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChart.tsx @@ -0,0 +1,389 @@ +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 new file mode 100644 index 0000000000..55e7e5aee6 --- /dev/null +++ b/frontend/src/modules/Traces/TraceGanttChart/TraceGanttChartHelpers.ts @@ -0,0 +1,40 @@ +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 new file mode 100644 index 0000000000..cce8f15870 --- /dev/null +++ b/frontend/src/modules/Traces/TraceGanttChart/index.tsx @@ -0,0 +1 @@ +export { default } from './TraceGanttChart'; diff --git a/frontend/src/modules/Traces/TraceGraph.css b/frontend/src/modules/Traces/TraceGraph.css new file mode 100644 index 0000000000..99648f61f2 --- /dev/null +++ b/frontend/src/modules/Traces/TraceGraph.css @@ -0,0 +1,59 @@ +.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 new file mode 100644 index 0000000000..2cd6e59c1d --- /dev/null +++ b/frontend/src/modules/Traces/TraceGraph.tsx @@ -0,0 +1,216 @@ +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 new file mode 100644 index 0000000000..c32524580c --- /dev/null +++ b/frontend/src/modules/Traces/TraceGraphColumn.tsx @@ -0,0 +1,76 @@ +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 new file mode 100644 index 0000000000..13658a60ec --- /dev/null +++ b/frontend/src/modules/Traces/TraceGraphDef.tsx @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000000..a16e487b3d --- /dev/null +++ b/frontend/src/modules/Traces/styles.ts @@ -0,0 +1,16 @@ +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 d7cf4c0869..f124268258 100644 --- a/frontend/src/pages/Trace/styles.ts +++ b/frontend/src/pages/Trace/styles.ts @@ -5,6 +5,7 @@ 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 deleted file mode 100644 index 0253cbeff0..0000000000 --- a/frontend/src/pages/TraceDetail/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const SPAN_DETAILS_LEFT_COL_WIDTH = 350; diff --git a/frontend/src/pages/TraceDetail/index.tsx b/frontend/src/pages/TraceDetail/index.tsx deleted file mode 100644 index bf29a5244b..0000000000 --- a/frontend/src/pages/TraceDetail/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -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 6ee8a31184..22d4fd9178 100644 --- a/frontend/src/store/actions/index.ts +++ b/frontend/src/store/actions/index.ts @@ -4,5 +4,7 @@ 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 942cb0dbed..7c43fc0427 100644 --- a/frontend/src/store/actions/metrics/getInitialData.ts +++ b/frontend/src/store/actions/metrics/getInitialData.ts @@ -6,7 +6,6 @@ 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'; @@ -65,7 +64,7 @@ export const GetInitialData = ( end: maxTime, service: props.serviceName, start: minTime, - step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }), + step, }), getTopEndPoints({ end: maxTime, diff --git a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts index 11d061e079..0ae6362eea 100644 --- a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts +++ b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts @@ -1,12 +1,11 @@ -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 { Props as GetSpanAggregateProps } from 'types/api/trace/getSpanAggregate'; +import getSpansAggregate from 'api/trace/getSpansAggregate'; import { GlobalReducer } from 'types/reducer/globalTime'; import { TraceReducer } from 'types/reducer/trace'; +import { notification } from 'antd'; export const GetSpansAggregate = ( props: GetSpansAggregateProps, @@ -53,7 +52,6 @@ export const GetSpansAggregate = ( offset: props.current * props.pageSize - props.pageSize, selectedTags: props.selectedTags, isFilterExclude: traces.isFilterExclude, - order: props.order, }); if (response.statusCode === 200) { @@ -114,5 +112,4 @@ 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 new file mode 100644 index 0000000000..35502674fd --- /dev/null +++ b/frontend/src/store/actions/traceFilters.ts @@ -0,0 +1,38 @@ +// 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 8ac074cd2f..72200fb5eb 100644 --- a/frontend/src/store/actions/traces.ts +++ b/frontend/src/store/actions/traces.ts @@ -43,10 +43,6 @@ 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 2aaa38f9c3..6c2902c1b3 100644 --- a/frontend/src/store/actions/types.ts +++ b/frontend/src/store/actions/types.ts @@ -1,4 +1,6 @@ import { serviceMapItemAction, servicesAction } from './serviceMap'; +import { updateTraceFiltersAction } from './traceFilters'; +import { FetchTraceItemAction, FetchTracesAction } from './traces'; import { getUsageDataAction } from './usage'; export enum ActionTypes { @@ -11,4 +13,10 @@ export enum ActionTypes { fetchTraceItem = 'FETCH_TRACE_ITEM', } -export type Action = getUsageDataAction | servicesAction | serviceMapItemAction; +export type Action = + | FetchTraceItemAction + | FetchTracesAction + | updateTraceFiltersAction + | getUsageDataAction + | servicesAction + | serviceMapItemAction; diff --git a/frontend/src/store/reducers/index.ts b/frontend/src/store/reducers/index.ts index 690464b215..9c2bdecb1f 100644 --- a/frontend/src/store/reducers/index.ts +++ b/frontend/src/store/reducers/index.ts @@ -6,10 +6,14 @@ 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 new file mode 100644 index 0000000000..d234015c73 --- /dev/null +++ b/frontend/src/store/reducers/traceFilters.ts @@ -0,0 +1,29 @@ +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 new file mode 100644 index 0000000000..afe8436818 --- /dev/null +++ b/frontend/src/store/reducers/traces.ts @@ -0,0 +1,33 @@ +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 3263621009..0c2232efa9 100644 --- a/frontend/src/types/api/trace/getSpanAggregate.ts +++ b/frontend/src/types/api/trace/getSpanAggregate.ts @@ -7,7 +7,6 @@ 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 deleted file mode 100644 index fc05448650..0000000000 --- a/frontend/src/types/api/trace/getTraceItem.ts +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index dd674b33e6..0000000000 --- a/frontend/src/types/global.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -// 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 new file mode 100644 index 0000000000..6431bc5fc6 --- /dev/null +++ b/frontend/src/typings/react-app-env.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/src/typings/react-graph-vis.ts b/frontend/src/typings/react-graph-vis.ts new file mode 100644 index 0000000000..bef518e573 --- /dev/null +++ b/frontend/src/typings/react-graph-vis.ts @@ -0,0 +1,39 @@ +//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 deleted file mode 100644 index 2938507603..0000000000 --- a/frontend/src/utils/getSpanTreeMetadata.ts +++ /dev/null @@ -1,42 +0,0 @@ -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 0f50cf6d40..e56e9854ea 100644 --- a/frontend/src/utils/spanToTree.ts +++ b/frontend/src/utils/spanToTree.ts @@ -1,10 +1,9 @@ -import { cloneDeep } from 'lodash-es'; -import { ITraceTree, Span } from 'types/api/trace/getTraceItem'; +import { pushDStree, RefItem, span } from 'store/actions'; // PNOTE - should the data be taken from redux or only through props? - Directly as arguments -export const spanToTreeUtil = (originalList: Span[]): ITraceTree => { +export const spanToTreeUtil = (spanlist: span[]): pushDStree => { // Initializing tree. What should be returned is trace is empty? We should have better error handling - let tree: ITraceTree = { + let tree: pushDStree = { id: 'empty', name: 'default', value: 0, @@ -12,12 +11,8 @@ export const spanToTreeUtil = (originalList: Span[]): ITraceTree => { startTime: 0, tags: [], children: [], - serviceColour: '', - serviceName: '', }; - let spanlist = cloneDeep(originalList); - // let spans :spanItem[]= trace.spans; if (spanlist) { @@ -29,15 +24,12 @@ export const spanToTreeUtil = (originalList: Span[]): ITraceTree => { //May1 //https://stackoverflow.com/questions/13315131/enforcing-the-type-of-the-indexed-members-of-a-typescript-object - const mapped_array: { [id: string]: Span } = {}; - const originalListArray: { [id: string]: Span } = {}; + const mapped_array: { [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 ITraceTree + mapped_array[spanlist[i][1]][10] = []; //initialising the 10th element in the span data structure which is array + // of type pushDStree // console.log('IDs while creating mapped array') // console.log(`SpanID is ${spanlist[i][1]}\n`); } @@ -62,22 +54,15 @@ export const spanToTreeUtil = (originalList: Span[]): ITraceTree => { } } - const push_object: ITraceTree = { + const push_object: pushDStree = { id: child_span[1], - name: child_span[4], + name: child_span[3] + ': ' + 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') { @@ -85,7 +70,7 @@ export const spanToTreeUtil = (originalList: Span[]): ITraceTree => { } else { refArray = referencesArr; } - const references = []; + const references: RefItem[] = []; refArray.forEach((element) => { element = element @@ -131,5 +116,5 @@ export const spanToTreeUtil = (originalList: Span[]): ITraceTree => { } // 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 deleted file mode 100644 index 6aabbb267e..0000000000 --- a/frontend/src/utils/toFixed.ts +++ /dev/null @@ -1,6 +0,0 @@ -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 3ff85194f8..b43a985abc 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -18,8 +18,7 @@ "isolatedModules": true, "noEmit": true, "baseUrl": "./src", - "downlevelIteration": true, - "plugins": [{ "name": "typescript-plugin-css-modules" }] + "downlevelIteration": true }, "exclude": ["node_modules"], "include": ["./src"] diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 549f98e0d1..5b94f0f903 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -61,15 +61,7 @@ const config = { }, { test: /\.css$/, - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - modules: true, - }, - }, - ], + use: ['style-loader', 'css-loader'], }, { test: /\.(jpe?g|png|gif|svg)$/i, @@ -90,9 +82,6 @@ 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 7ccc97d6ba..291fa84e15 100644 --- a/frontend/webpack.config.prod.js +++ b/frontend/webpack.config.prod.js @@ -74,15 +74,7 @@ const config = { }, { test: /\.css$/, - use: [ - MiniCssExtractPlugin.loader, - { - loader: 'css-loader', - options: { - modules: true, - }, - }, - ], + use: [MiniCssExtractPlugin.loader, 'css-loader'], }, { test: /\.(jpe?g|png|gif|svg)$/i, @@ -103,9 +95,6 @@ const config = { }, { loader: 'css-loader', - options: { - modules: true, - }, }, { loader: 'less-loader', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index dd0fab967c..9bdfc03964 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1773,25 +1773,6 @@ 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" @@ -2165,11 +2146,6 @@ 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" @@ -2645,11 +2621,6 @@ 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" @@ -3566,11 +3537,6 @@ 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" @@ -3886,7 +3852,7 @@ chalk@2.1.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" -chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, 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== @@ -3931,21 +3897,6 @@ 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" @@ -4108,27 +4059,11 @@ 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.0.0, color-name@~1.1.4: +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" @@ -4285,7 +4220,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.3.1: +copy-to-clipboard@^3.2.0: 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== @@ -4426,14 +4361,6 @@ 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" @@ -4465,13 +4392,6 @@ 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" @@ -4483,14 +4403,6 @@ 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" @@ -4518,16 +4430,6 @@ 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" @@ -4616,7 +4518,7 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.2, csstype@^3.0.6: +csstype@^3.0.2: version "3.0.10" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== @@ -5270,13 +5172,6 @@ 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" @@ -5572,11 +5467,6 @@ 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" @@ -5615,11 +5505,6 @@ 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" @@ -5676,13 +5561,6 @@ 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" @@ -6216,26 +6094,11 @@ 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" @@ -6511,13 +6374,6 @@ 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" @@ -6622,7 +6478,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.6: +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -6988,11 +6844,6 @@ 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" @@ -7000,13 +6851,6 @@ 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" @@ -7014,11 +6858,6 @@ 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" @@ -7050,11 +6889,6 @@ 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" @@ -7122,13 +6956,6 @@ 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" @@ -7200,11 +7027,6 @@ 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" @@ -7984,11 +7806,6 @@ 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" @@ -8092,11 +7909,6 @@ 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" @@ -8208,7 +8020,7 @@ less-plugin-npm-import@^2.1.0: promise "~7.0.1" resolve "~1.1.6" -less@^4.1.1, less@^4.1.2: +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== @@ -8246,7 +8058,7 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lilconfig@^2.0.3, lilconfig@^2.0.4: +lilconfig@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== @@ -8310,16 +8122,6 @@ 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" @@ -8373,11 +8175,6 @@ 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" @@ -8418,7 +8215,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.17.4, 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.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8683,11 +8480,6 @@ 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" @@ -8741,20 +8533,6 @@ 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" @@ -8765,11 +8543,6 @@ 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" @@ -9319,11 +9092,14 @@ 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" @@ -9332,6 +9108,7 @@ 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== @@ -9567,41 +9344,6 @@ 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" @@ -9802,11 +9544,6 @@ 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" @@ -9820,24 +9557,6 @@ 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" @@ -10602,31 +10321,6 @@ 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" @@ -10861,11 +10555,6 @@ 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" @@ -10964,13 +10653,6 @@ 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" @@ -11007,7 +10689,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.2, 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.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -11027,16 +10709,7 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -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: +sax@>=0.6.0, 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== @@ -11084,11 +10757,6 @@ 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" @@ -11201,11 +10869,6 @@ 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" @@ -11304,13 +10967,6 @@ 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" @@ -11392,17 +11048,12 @@ 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.2: +source-map-resolve@^0.5.0: 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== @@ -11434,11 +11085,6 @@ 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" @@ -11454,11 +11100,6 @@ 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" @@ -11540,13 +11181,6 @@ 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" @@ -11554,28 +11188,6 @@ 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" @@ -11773,25 +11385,6 @@ 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" @@ -11809,7 +11402,7 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@^5.3.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== @@ -12003,11 +11596,6 @@ 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" @@ -12136,11 +11724,6 @@ 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" @@ -12183,7 +11766,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.1.0, tslib@^2.3.0: +tslib@^2.0.3, 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== @@ -12266,25 +11849,6 @@ 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 7526505d33..fb5c460747 100644 --- a/pkg/query-service/model/response.go +++ b/pkg/query-service/model/response.go @@ -205,8 +205,17 @@ 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, item.Events, 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, errorEvent, item.HasError} return returnArray } diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 8a60be19da..5de8392e0c 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -22,7 +22,6 @@ const ( ) const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz" -const IP_NOT_FOUND_PLACEHOLDER = "NA" var telemetry *Telemetry var once sync.Once @@ -60,7 +59,7 @@ func createTelemetry() { // Get preferred outbound ip of this machine func getOutboundIP() string { - ip := []byte(IP_NOT_FOUND_PLACEHOLDER) + ip := []byte("NA") resp, err := http.Get("https://api.ipify.org?format=text") if err != nil { @@ -117,7 +116,7 @@ func (a *Telemetry) SendEvent(event string, data map[string]interface{}) { } userId := a.ipAddress - if a.isTelemetryAnonymous() || userId == IP_NOT_FOUND_PLACEHOLDER { + if a.isTelemetryAnonymous() { userId = a.GetDistinctId() } diff --git a/sample-apps/hotrod/README.md b/sample-apps/hotrod/README.md deleted file mode 100644 index 1907c23dfc..0000000000 --- a/sample-apps/hotrod/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# 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 deleted file mode 100755 index f73f89c1a6..0000000000 --- a/sample-apps/hotrod/hotrod-delete.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/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 deleted file mode 100755 index f6f3845205..0000000000 --- a/sample-apps/hotrod/hotrod-install.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/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 deleted file mode 100644 index 6fdd6dd9ae..0000000000 --- a/sample-apps/hotrod/hotrod-template.yaml +++ /dev/null @@ -1,223 +0,0 @@ -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 From eb4abe900c4363e19a3a042d1504af2813a8ce37 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 28 Mar 2022 09:14:40 +0530 Subject: [PATCH 003/154] chore: generate uuid on server side --- pkg/query-service/app/dashboards/model.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/app/dashboards/model.go b/pkg/query-service/app/dashboards/model.go index 480fadf69d..77b66046c0 100644 --- a/pkg/query-service/app/dashboards/model.go +++ b/pkg/query-service/app/dashboards/model.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/gosimple/slug" "github.com/jmoiron/sqlx" "go.signoz.io/query-service/model" @@ -109,8 +110,7 @@ func CreateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError) dash.CreatedAt = time.Now() dash.UpdatedAt = time.Now() dash.UpdateSlug() - // dash.Uuid = uuid.New().String() - dash.Uuid = dash.Data["uuid"].(string) + dash.Uuid = uuid.New().String() map_data, err := json.Marshal(dash.Data) if err != nil { From 225a345baa29ed4cf8fa74e18b43c82194830c78 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Tue, 29 Mar 2022 00:02:16 +0530 Subject: [PATCH 004/154] chore: getTagValue api is added --- frontend/src/api/trace/getTagValue.ts | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 frontend/src/api/trace/getTagValue.ts diff --git a/frontend/src/api/trace/getTagValue.ts b/frontend/src/api/trace/getTagValue.ts new file mode 100644 index 0000000000..25156d32ef --- /dev/null +++ b/frontend/src/api/trace/getTagValue.ts @@ -0,0 +1,28 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/trace/getTagValue'; + +const getTagValue = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post(`/getTagValues`, { + start: props.start.toString(), + end: props.end.toString(), + tagKey: props.tagKey, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getTagValue; From d4d1104a5328c574165f99da2ed02d9fe9c834f1 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Tue, 29 Mar 2022 00:02:56 +0530 Subject: [PATCH 005/154] WIP: value suggestion is added --- .../AllTags/Tag/DebounceSelect/index.tsx | 63 +++++++++++++++++++ .../Trace/Search/AllTags/Tag/config.ts | 32 ++++++++++ .../Trace/Search/AllTags/Tag/index.tsx | 25 +++++--- .../Trace/Search/AllTags/Tag/styles.ts | 6 -- frontend/src/types/api/trace/getTagValue.ts | 11 ++++ 5 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx create mode 100644 frontend/src/container/Trace/Search/AllTags/Tag/config.ts create mode 100644 frontend/src/types/api/trace/getTagValue.ts diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx new file mode 100644 index 0000000000..1971947820 --- /dev/null +++ b/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx @@ -0,0 +1,63 @@ +import { Select, Spin } from 'antd'; +import { SelectProps } from 'antd/es/select'; +import debounce from 'lodash-es/debounce'; +import React, { useRef, useState } from 'react'; + +export interface DebounceSelectProps + extends Omit, 'options' | 'children'> { + fetchOptions: (search: string) => Promise; + debounceTimeout: number; +} + +function DebounceSelect< + ValueType extends { + key?: string; + label: React.ReactNode; + value: string | number; + } = never +>({ + fetchOptions, + debounceTimeout = 800, + ...props +}: DebounceSelectProps): JSX.Element { + const [fetching, setFetching] = useState(false); + const [options, setOptions] = useState([]); + const fetchRef = useRef(0); + + const debounceFetcher = React.useMemo(() => { + const loadOptions = (value: string): void => { + fetchRef.current += 1; + const fetchId = fetchRef.current; + setOptions([]); + setFetching(true); + + fetchOptions(value).then((newOptions) => { + if (fetchId !== fetchRef.current) { + // for fetch callback order + return; + } + + setOptions(newOptions); + setFetching(false); + }); + }; + + return debounce(loadOptions, debounceTimeout); + }, [fetchOptions, debounceTimeout]); + + return ( + + labelInValue + filterOption={false} + onSearch={debounceFetcher} + notFoundContent={fetching ? : null} + style={{ minWidth: '170px' }} + // as all other props are from SelectProps only + // eslint-disable-next-line react/jsx-props-no-spreading + {...props} + options={options} + /> + ); +} + +export default DebounceSelect; diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/config.ts b/frontend/src/container/Trace/Search/AllTags/Tag/config.ts new file mode 100644 index 0000000000..07ee042621 --- /dev/null +++ b/frontend/src/container/Trace/Search/AllTags/Tag/config.ts @@ -0,0 +1,32 @@ +import getTagValue from 'api/trace/getTagValue'; + +// Usage of DebounceSelect +export interface TagValue { + label: string; + value: string; +} + +export async function fetchTag( + globalStart: number, + globalEnd: number, + tagKey: string, +): Promise { + const response = await getTagValue({ + end: globalEnd, + start: globalStart, + tagKey, + }); + + if (response.statusCode !== 200 || !response.payload) { + return []; + } + + console.log(response.payload); + + return [ + { + label: 'asd', + value: 'asd', + }, + ]; +} diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx index e6ad17956f..8bcf15757a 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx @@ -3,14 +3,12 @@ import { Select } from 'antd'; import React from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; import { TraceReducer } from 'types/reducer/trace'; -import { - Container, - IconContainer, - SelectComponent, - ValueSelect, -} from './styles'; +import { fetchTag, TagValue } from './config'; +import DebounceSelect from './DebounceSelect'; +import { Container, IconContainer, SelectComponent } from './styles'; import TagsKey from './TagKey'; const { Option } = Select; @@ -35,6 +33,9 @@ const AllMenu: AllMenuProps[] = [ function SingleTags(props: AllTagsProps): JSX.Element { const traces = useSelector((state) => state.traces); + const globalReducer = useSelector( + (state) => state.globalTime, + ); const { tag, onCloseHandler, setLocalSelectedTags, index } = props; const { @@ -80,7 +81,15 @@ function SingleTags(props: AllTagsProps): JSX.Element { ))} - => + fetchTag(globalReducer.minTime, globalReducer.maxTime, selectedKey[0]) + } + debounceTimeout={300} + mode="tags" + /> + + {/* { setLocalSelectedTags((tags) => [ @@ -94,7 +103,7 @@ function SingleTags(props: AllTagsProps): JSX.Element { ]); }} mode="tags" - /> + /> */} onDeleteTagHandler(index)}> diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts b/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts index 8da702197d..347bc287f2 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts +++ b/frontend/src/container/Trace/Search/AllTags/Tag/styles.ts @@ -15,12 +15,6 @@ export const SelectComponent = styled(Select)` } `; -export const ValueSelect = styled(Select)` - &&& { - width: 100%; - } -`; - export const Container = styled.div` &&& { display: flex; diff --git a/frontend/src/types/api/trace/getTagValue.ts b/frontend/src/types/api/trace/getTagValue.ts new file mode 100644 index 0000000000..221be8dfba --- /dev/null +++ b/frontend/src/types/api/trace/getTagValue.ts @@ -0,0 +1,11 @@ +import { GlobalReducer } from 'types/reducer/globalTime'; + +export interface Props { + start: GlobalReducer['minTime']; + end: GlobalReducer['maxTime']; + tagKey: string; +} + +export interface PayloadProps { + key: string; +} From 5556d1d6fc8f87f343669a36ba0b49745f57f6f9 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Tue, 29 Mar 2022 09:59:50 +0530 Subject: [PATCH 006/154] feat: tag value suggestion is updated --- .../AllTags/Tag/DebounceSelect/index.tsx | 2 +- .../Trace/Search/AllTags/Tag/config.ts | 12 ++++------- .../Trace/Search/AllTags/Tag/index.tsx | 21 ++++++++++--------- frontend/src/types/api/trace/getTagValue.ts | 6 ++++-- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx index 1971947820..2576e782e0 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx @@ -51,7 +51,7 @@ function DebounceSelect< filterOption={false} onSearch={debounceFetcher} notFoundContent={fetching ? : null} - style={{ minWidth: '170px' }} + style={{ width: '170px' }} // as all other props are from SelectProps only // eslint-disable-next-line react/jsx-props-no-spreading {...props} diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/config.ts b/frontend/src/container/Trace/Search/AllTags/Tag/config.ts index 07ee042621..ce414a5f15 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/config.ts +++ b/frontend/src/container/Trace/Search/AllTags/Tag/config.ts @@ -21,12 +21,8 @@ export async function fetchTag( return []; } - console.log(response.payload); - - return [ - { - label: 'asd', - value: 'asd', - }, - ]; + return response.payload.map((e) => ({ + label: e.tagValues, + value: e.tagValues, + })); } diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx index 8bcf15757a..37b42347a1 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx @@ -83,27 +83,22 @@ function SingleTags(props: AllTagsProps): JSX.Element { => - fetchTag(globalReducer.minTime, globalReducer.maxTime, selectedKey[0]) + fetchTag(globalReducer.minTime, globalReducer.maxTime, selectedKey[index]) } debounceTimeout={300} - mode="tags" - /> - - {/* { + onSelect={(value: Value): void => { setLocalSelectedTags((tags) => [ ...tags.slice(0, index), { Key: selectedKey, Operator: selectedOperator, - Values: value as string[], + Values: [...selectedValues, value.value], }, ...tags.slice(index + 1, tags.length), ]); }} - mode="tags" - /> */} + mode="multiple" + /> onDeleteTagHandler(index)}> @@ -121,4 +116,10 @@ interface AllTagsProps { >; } +interface Value { + key: string; + label: string; + value: string; +} + export default SingleTags; diff --git a/frontend/src/types/api/trace/getTagValue.ts b/frontend/src/types/api/trace/getTagValue.ts index 221be8dfba..e90975d1d5 100644 --- a/frontend/src/types/api/trace/getTagValue.ts +++ b/frontend/src/types/api/trace/getTagValue.ts @@ -6,6 +6,8 @@ export interface Props { tagKey: string; } -export interface PayloadProps { - key: string; +interface Value { + tagValues: string; } + +export type PayloadProps = Value[]; From 739946fa47d2ead6aef98e963ab71262eb7df3b6 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 29 Mar 2022 16:05:08 +0530 Subject: [PATCH 007/154] fix: over memory allocation on Graph on big time range --- .../src/components/Graph/Plugin/EmptyGraph.ts | 17 ++++++++++ frontend/src/components/Graph/hasData.ts | 19 +++++++++++ frontend/src/components/Graph/index.tsx | 13 ++++++-- .../GridGraphLayout/Graph/FullView/index.tsx | 32 ------------------- 4 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 frontend/src/components/Graph/Plugin/EmptyGraph.ts create mode 100644 frontend/src/components/Graph/hasData.ts diff --git a/frontend/src/components/Graph/Plugin/EmptyGraph.ts b/frontend/src/components/Graph/Plugin/EmptyGraph.ts new file mode 100644 index 0000000000..0f0577bb80 --- /dev/null +++ b/frontend/src/components/Graph/Plugin/EmptyGraph.ts @@ -0,0 +1,17 @@ +import { grey } from '@ant-design/colors'; +import { Chart } from 'chart.js'; + +export const emptyGraph = { + id: 'emptyChart', + afterDraw(chart: Chart): void { + const { height, width, ctx } = chart; + chart.clear(); + ctx.save(); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = '1.5rem sans-serif'; + ctx.fillStyle = `${grey.primary}`; + ctx.fillText('No data to display', width / 2, height / 2); + ctx.restore(); + }, +}; diff --git a/frontend/src/components/Graph/hasData.ts b/frontend/src/components/Graph/hasData.ts new file mode 100644 index 0000000000..acaa4fac1e --- /dev/null +++ b/frontend/src/components/Graph/hasData.ts @@ -0,0 +1,19 @@ +/* eslint-disable no-restricted-syntax */ +import { ChartData } from 'chart.js'; + +export const hasData = (data: ChartData): boolean => { + const { datasets = [] } = data; + let hasData = false; + try { + for (const dataset of datasets) { + if (dataset.data.length > 0 && dataset.data.some((item) => item !== 0)) { + hasData = true; + break; + } + } + } catch (error) { + console.error(error); + } + + return hasData; +}; diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index a8f668235d..e51ff21616 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -27,7 +27,9 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; +import { hasData } from './hasData'; import { legend } from './Plugin'; +import { emptyGraph } from './Plugin/EmptyGraph'; import { LegendsContainer } from './styles'; import { useXAxisTimeUnit } from './xAxisConfig'; import { getYAxisFormattedValue } from './yAxisConfig'; @@ -128,6 +130,7 @@ function Graph({ grid: { display: true, color: getGridColor(), + drawTicks: true, }, adapters: { date: chartjsAdapter, @@ -180,12 +183,18 @@ function Graph({ } }, }; - + const chartHasData = hasData(data); + const chartPlugins = []; + if (chartHasData) { + chartPlugins.push(legend(name, data.datasets.length > 3)); + } else { + chartPlugins.push(emptyGraph); + } lineChartRef.current = new Chart(chartRef.current, { type, data, options, - plugins: [legend(name, data.datasets.length > 3)], + plugins: chartPlugins, }); } }, [ diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 0f0ce7b1fb..6e38e05536 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -166,38 +166,6 @@ function FullView({ ); } - if (state.loading === false && state.payload.datasets.length === 0) { - return ( - <> - {fullViewOptions && ( - - - - - )} - - {noDataGraph ? ( - - ) : ( - - No Data - - )} - - ); - } - return ( <> {fullViewOptions && ( From 5a5aca211381e40676cfcba3208ccb193751aea8 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 29 Mar 2022 16:06:27 +0530 Subject: [PATCH 008/154] chore: remove unused code --- .../Graph/FullView/EmptyGraph.tsx | 91 ------------------- .../GridGraphLayout/Graph/FullView/index.tsx | 1 - 2 files changed, 92 deletions(-) delete mode 100644 frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx deleted file mode 100644 index c284d83b7c..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import Graph, { GraphOnClickHandler } from 'components/Graph'; -import { timePreferance } from 'container/NewWidget/RightContainer/timeItems'; -import GetMaxMinTime from 'lib/getMaxMinTime'; -import { colors } from 'lib/getRandomColor'; -import getStartAndEndTime from 'lib/getStartAndEndTime'; -import getTimeString from 'lib/getTimeString'; -import React, { useCallback } from 'react'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import { Widgets } from 'types/api/dashboard/getAll'; -import { GlobalReducer } from 'types/reducer/globalTime'; - -function EmptyGraph({ - selectedTime, - widget, - onClickHandler, -}: EmptyGraphProps): JSX.Element { - const { minTime, maxTime, loading } = useSelector( - (state) => state.globalTime, - ); - - const maxMinTime = GetMaxMinTime({ - graphType: widget.panelTypes, - maxTime, - minTime, - }); - - const { end, start } = getStartAndEndTime({ - type: selectedTime.enum, - maxTime: maxMinTime.maxTime, - minTime: maxMinTime.minTime, - }); - - const dateFunction = useCallback(() => { - if (!loading) { - const dates: Date[] = []; - - const startString = getTimeString(start); - const endString = getTimeString(end); - - const parsedStart = parseInt(startString, 10); - const parsedEnd = parseInt(endString, 10); - - let startDate = parsedStart; - const endDate = parsedEnd; - - while (endDate >= startDate) { - const newDate = new Date(startDate); - - startDate += 20000; - - dates.push(newDate); - } - return dates; - } - return []; - }, [start, end, loading]); - - const date = dateFunction(); - - return ( - - ); -} - -interface EmptyGraphProps { - selectedTime: timePreferance; - widget: Widgets; - onClickHandler: GraphOnClickHandler | undefined; -} - -export default EmptyGraph; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 6e38e05536..fcc656318c 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -23,7 +23,6 @@ import { AppState } from 'store/reducers'; import { Widgets } from 'types/api/dashboard/getAll'; import { GlobalReducer } from 'types/reducer/globalTime'; -import EmptyGraph from './EmptyGraph'; import { NotFoundContainer, TimeContainer } from './styles'; function FullView({ From 20e924b116030ff48c30e714b53a6d86fb54c646 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Fri, 1 Apr 2022 15:12:30 +0530 Subject: [PATCH 009/154] feat: S3 TTL support --- frontend/src/api/disks/getDisks.ts | 24 +++ .../container/GeneralSettings/Retention.tsx | 142 +++++++++--------- .../src/container/GeneralSettings/index.tsx | 113 +++++++++++--- .../src/container/GeneralSettings/styles.ts | 16 +- .../src/container/GeneralSettings/utils.ts | 41 +++++ frontend/src/lib/convertIntoHr.ts | 4 +- frontend/src/lib/getSettingsPeroid.ts | 4 +- .../src/pages/AlertChannelCreate/index.tsx | 4 +- frontend/src/pages/AllAlertChannels/index.tsx | 5 +- frontend/src/pages/Settings/index.tsx | 4 +- frontend/src/types/api/disks/getDisks.ts | 4 + 11 files changed, 241 insertions(+), 120 deletions(-) create mode 100644 frontend/src/api/disks/getDisks.ts create mode 100644 frontend/src/container/GeneralSettings/utils.ts create mode 100644 frontend/src/types/api/disks/getDisks.ts diff --git a/frontend/src/api/disks/getDisks.ts b/frontend/src/api/disks/getDisks.ts new file mode 100644 index 0000000000..70da1bddc9 --- /dev/null +++ b/frontend/src/api/disks/getDisks.ts @@ -0,0 +1,24 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { IDiskType } from 'types/api/disks/getDisks'; + +const getDisks = async (): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.get(`/disks`); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getDisks; diff --git a/frontend/src/container/GeneralSettings/Retention.tsx b/frontend/src/container/GeneralSettings/Retention.tsx index b2f5cb5a09..64591b9b59 100644 --- a/frontend/src/container/GeneralSettings/Retention.tsx +++ b/frontend/src/container/GeneralSettings/Retention.tsx @@ -1,108 +1,108 @@ import { DownOutlined } from '@ant-design/icons'; -import { Button, Menu } from 'antd'; -import React from 'react'; +import { Button, Col, Menu, Row, Select } from 'antd'; +import { find } from 'lodash-es'; +import React, { useCallback, useEffect, useState } from 'react'; -import { SettingPeroid } from '.'; +import { Dropdown, Input, RetentionContainer, Typography } from './styles'; import { - Dropdown, - Input, - RetentionContainer, - TextContainer, - Typography, -} from './styles'; + convertHoursValueToRelevantUnit, + ITimeUnit, + SettingPeriod, + TimeUnits, +} from './utils'; + +const { Option } = Select; function Retention({ retentionValue, - setRentionValue, - selectedRetentionPeroid, - setSelectedRetentionPeroid, + setRetentionValue, text, }: RetentionProps): JSX.Element { - const options: Option[] = [ - { - key: 'hr', - value: 'Hrs', - }, - { - key: 'day', - value: 'Days', - }, - { - key: 'month', - value: 'Months', - }, - ]; - - const onClickHandler = ( - e: { key: string }, - func: React.Dispatch>, - ): void => { - const selected = e.key as SettingPeroid; - func(selected); - }; - - const menu = ( - onClickHandler(e, setSelectedRetentionPeroid)}> - {options.map((option) => ( - {option.value} - ))} - + const { + value: initialValue, + timeUnitValue: initialTimeUnitValue, + } = convertHoursValueToRelevantUnit(retentionValue); + const [selectedTimeUnit, setSelectTimeUnit] = useState(initialTimeUnitValue); + const [selectedValue, setSelectedValue] = useState( + initialValue, ); - const currentSelectedOption = (option: SettingPeroid): string | undefined => { - return options.find((e) => e.key === option)?.value; + const menuItems = TimeUnits.map((option) => ( + + )); + + const currentSelectedOption = (option: SettingPeriod): void => { + const selectedValue = find(TimeUnits, (e) => e.value === option)?.value; + if (selectedValue) setSelectTimeUnit(selectedValue); }; + useEffect(() => { + const inverseMultiplier = find( + TimeUnits, + (timeUnit) => timeUnit.value === selectedTimeUnit, + )?.multiplier; + if (selectedValue && inverseMultiplier) { + setRetentionValue(selectedValue * (1 / inverseMultiplier)); + } + }, [selectedTimeUnit, selectedValue, setRetentionValue]); + const onChangeHandler = ( e: React.ChangeEvent, - func: React.Dispatch>, + func: React.Dispatch>, ): void => { const { value } = e.target; const integerValue = parseInt(value, 10); if (value.length > 0 && integerValue.toString() === value) { - const parsedValue = Math.abs(integerValue).toString(); + const parsedValue = Math.abs(integerValue); func(parsedValue); } if (value.length === 0) { - func(''); + func(null); } }; return ( - - {text} - - - onChangeHandler(e, setRentionValue)} - /> - - - - + + + + {text} + + + +
+ = 0 ? selectedValue : ''} + onChange={(e): void => onChangeHandler(e, setSelectedValue)} + style={{ width: 75 }} + /> + +
+ + ); } -interface Option { - key: SettingPeroid; - value: string; -} - interface RetentionProps { - retentionValue: string; + retentionValue: number; text: string; - setRentionValue: React.Dispatch>; - selectedRetentionPeroid: SettingPeroid; - setSelectedRetentionPeroid: React.Dispatch< - React.SetStateAction - >; + setRetentionValue: React.Dispatch>; } export default Retention; diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index e741e81886..7796e38d5f 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -1,4 +1,5 @@ -import { Button, Modal, notification, Typography } from 'antd'; +import { Button, Col, Modal, notification, Row, Typography } from 'antd'; +import getDisks from 'api/disks/getDisks'; import getRetentionperoidApi from 'api/settings/getRetention'; import setRetentionApi from 'api/settings/setRetention'; import Spinner from 'components/Spinner'; @@ -12,7 +13,6 @@ import { PayloadProps } from 'types/api/settings/getRetention'; import Retention from './Retention'; import { ButtonContainer, - Container, ErrorText, ErrorTextContainer, ToolTipContainer, @@ -22,16 +22,15 @@ function GeneralSettings(): JSX.Element { const [ selectedMetricsPeroid, setSelectedMetricsPeroid, - ] = useState('month'); + ] = useState('month'); const [notifications, Element] = notification.useNotification(); - const [retentionPeroidMetrics, setRetentionPeroidMetrics] = useState( '', ); const [modal, setModal] = useState(false); const [postApiLoading, setPostApiLoading] = useState(false); - const [selectedTracePeroid, setSelectedTracePeroid] = useState( + const [selectedTracePeroid, setSelectedTracePeroid] = useState( 'hr', ); @@ -39,6 +38,30 @@ function GeneralSettings(): JSX.Element { const [isDefaultMetrics, setIsDefaultMetrics] = useState(false); const [isDefaultTrace, setIsDefaultTrace] = useState(false); + const [availableDisks, setAvailableDisks] = useState(null); + + useEffect(() => { + getDisks().then((resp) => console.log({ disks: resp })) + }, []) + const currentTTLValues = { + metrics_ttl_duration_hrs: 24 * 30 * 10, + metrics_move_ttl_duration_hrs: -1, + traces_ttl_duration_hrs: -1, + traces_move_ttl_duration_hrs: -1, + }; + const [metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod] = useState< + number | null + >(currentTTLValues.metrics_ttl_duration_hrs); + const [metricsS3RetentionPeriod, setMetricsS3RetentionPeriod] = useState< + number | null + >(currentTTLValues.metrics_move_ttl_duration_hrs); + const [tracesTotalRetentionPeriod, setTracesTotalRetentionPeriod] = useState< + number | null + >(currentTTLValues.traces_ttl_duration_hrs); + const [tracesS3RetentionPeriod, setTracesS3RetentionPeriod] = useState< + number | null + >(currentTTLValues.traces_move_ttl_duration_hrs); + const onModalToggleHandler = (): void => { setModal((modal) => !modal); }; @@ -65,6 +88,39 @@ function GeneralSettings(): JSX.Element { setIsDefaultTrace(false); } }; + // const retentionRenderConfig = () => { }; + const renderConfig = [ + { + name: 'Metrics', + retentionFields: [ + { + name: 'Total Retention Period', + value: metricsTotalRetentionPeriod, + setValue: setMetricsTotalRetentionPeriod, + }, + { + name: `Move to S3\n(should be lower than total retention period)`, + value: metricsS3RetentionPeriod, + setValue: setMetricsS3RetentionPeriod, + }, + ], + }, + { + name: 'Traces', + retentionFields: [ + { + name: 'Total Retention Period', + value: tracesTotalRetentionPeriod, + setValue: setTracesTotalRetentionPeriod, + }, + { + name: `Move to S3\n(should be lower than total retention period)`, + value: tracesS3RetentionPeriod, + setValue: setTracesS3RetentionPeriod, + }, + ], + }, + ]; useEffect(() => { if (!loading && payload !== undefined) { @@ -96,7 +152,7 @@ function GeneralSettings(): JSX.Element { const retentionMetricsValue = retentionPeroidMetrics === '0' && - (payload?.metrics_ttl_duration_hrs || 0) < 0 + (payload?.metrics_ttl_duration_hrs || 0) < 0 ? payload?.metrics_ttl_duration_hrs || 0 : parseInt(retentionPeroidMetrics, 10); @@ -180,9 +236,8 @@ function GeneralSettings(): JSX.Element { const errorText = getErrorText(); return ( - + {Element} - {errorText ? ( {errorText} @@ -204,22 +259,30 @@ function GeneralSettings(): JSX.Element { /> )} + + {renderConfig.map((category): JSX.Element | null => { + if ( + Array.isArray(category.retentionFields) && + category.retentionFields.length > 0 + ) { + return ( + + {category.name} - - - + {category.retentionFields.map((retentionField) => ( + + ))} + + ); + } + return null; + })} + - + ); } -export type SettingPeroid = 'hr' | 'day' | 'month'; +export type SettingPeriod = 'hr' | 'day' | 'month'; export default GeneralSettings; diff --git a/frontend/src/container/GeneralSettings/styles.ts b/frontend/src/container/GeneralSettings/styles.ts index 5233ce7e50..417fdb894e 100644 --- a/frontend/src/container/GeneralSettings/styles.ts +++ b/frontend/src/container/GeneralSettings/styles.ts @@ -1,16 +1,13 @@ import { + Col, Dropdown as DropDownComponent, Input as InputComponent, Typography as TypographyComponent, } from 'antd'; import styled from 'styled-components'; -export const RetentionContainer = styled.div` - width: 50%; - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; +export const RetentionContainer = styled(Col)` + margin: 0.75rem 0; `; export const Input = styled(InputComponent)` @@ -37,13 +34,6 @@ export const ButtonContainer = styled.div` } `; -export const Container = styled.div` - &&& { - display: flex; - flex-direction: column; - } -`; - export const Dropdown = styled(DropDownComponent)` &&& { display: flex; diff --git a/frontend/src/container/GeneralSettings/utils.ts b/frontend/src/container/GeneralSettings/utils.ts new file mode 100644 index 0000000000..caf0502d85 --- /dev/null +++ b/frontend/src/container/GeneralSettings/utils.ts @@ -0,0 +1,41 @@ +export type SettingPeriod = 'hr' | 'day' | 'month'; + +export interface ITimeUnit { + value: SettingPeriod; + key: string; + multiplier: number; +} +export const TimeUnits: ITimeUnit[] = [ + { + value: 'hr', + key: 'Hours', + multiplier: 1, + }, + { + value: 'day', + key: 'Days', + multiplier: 1 / 24, + }, + { + value: 'month', + key: 'Months', + multiplier: 1 / (24 * 30), + }, +]; + +export const convertHoursValueToRelevantUnit = ( + value: number, +): { value: number; timeUnitValue: SettingPeriod } => { + for (let idx = TimeUnits.length - 1; idx >= 0; idx -= 1) { + const timeUnit = TimeUnits[idx]; + const convertedValue = timeUnit.multiplier * value; + + if ( + convertedValue >= 1 && + convertedValue === parseInt(`${convertedValue}`, 10) + ) { + return { value: convertedValue, timeUnitValue: timeUnit.value }; + } + } + return { value, timeUnitValue: TimeUnits[0].value }; +}; diff --git a/frontend/src/lib/convertIntoHr.ts b/frontend/src/lib/convertIntoHr.ts index cd2d059e94..f7e408c2b1 100644 --- a/frontend/src/lib/convertIntoHr.ts +++ b/frontend/src/lib/convertIntoHr.ts @@ -1,6 +1,6 @@ -import { SettingPeroid } from 'container/GeneralSettings'; +import { SettingPeriod } from 'container/GeneralSettings'; -const converIntoHr = (value: number, peroid: SettingPeroid): number => { +const converIntoHr = (value: number, peroid: SettingPeriod): number => { if (peroid === 'day') { return value * 24; } diff --git a/frontend/src/lib/getSettingsPeroid.ts b/frontend/src/lib/getSettingsPeroid.ts index 72da4c3abb..0ea60ca0d5 100644 --- a/frontend/src/lib/getSettingsPeroid.ts +++ b/frontend/src/lib/getSettingsPeroid.ts @@ -1,4 +1,4 @@ -import { SettingPeroid } from 'container/GeneralSettings'; +import { SettingPeriod } from 'container/GeneralSettings'; const getSettingsPeroid = (hr: number): PayloadProps => { if (hr <= 0) { @@ -30,7 +30,7 @@ const getSettingsPeroid = (hr: number): PayloadProps => { interface PayloadProps { value: number; - peroid: SettingPeroid; + peroid: SettingPeriod; } export default getSettingsPeroid; diff --git a/frontend/src/pages/AlertChannelCreate/index.tsx b/frontend/src/pages/AlertChannelCreate/index.tsx index 59b2361cf8..1f316fe860 100644 --- a/frontend/src/pages/AlertChannelCreate/index.tsx +++ b/frontend/src/pages/AlertChannelCreate/index.tsx @@ -15,7 +15,7 @@ function SettingsPage(): JSX.Element { routes: [ { Component: GeneralSettings, - name: 'General Settings', + name: 'General', route: ROUTES.SETTINGS, }, { @@ -27,7 +27,7 @@ function SettingsPage(): JSX.Element { }, ], activeKey: - pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels', + pathName === ROUTES.SETTINGS ? 'General' : 'Alert Channels', }} /> ); diff --git a/frontend/src/pages/AllAlertChannels/index.tsx b/frontend/src/pages/AllAlertChannels/index.tsx index 8e64751064..b1d3820f66 100644 --- a/frontend/src/pages/AllAlertChannels/index.tsx +++ b/frontend/src/pages/AllAlertChannels/index.tsx @@ -14,7 +14,7 @@ function AllAlertChannels(): JSX.Element { routes: [ { Component: GeneralSettings, - name: 'General Settings', + name: 'General', route: ROUTES.SETTINGS, }, { @@ -23,8 +23,7 @@ function AllAlertChannels(): JSX.Element { route: ROUTES.ALL_CHANNELS, }, ], - activeKey: - pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels', + activeKey: pathName === ROUTES.SETTINGS ? 'General' : 'Alert Channels', }} /> ); diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx index e661fb6193..2b23cec2ec 100644 --- a/frontend/src/pages/Settings/index.tsx +++ b/frontend/src/pages/Settings/index.tsx @@ -14,7 +14,7 @@ function SettingsPage(): JSX.Element { routes: [ { Component: GeneralSettings, - name: 'General Settings', + name: 'General', route: ROUTES.SETTINGS, }, { @@ -24,7 +24,7 @@ function SettingsPage(): JSX.Element { }, ], activeKey: - pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General Settings', + pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General', }} /> ); diff --git a/frontend/src/types/api/disks/getDisks.ts b/frontend/src/types/api/disks/getDisks.ts new file mode 100644 index 0000000000..fc917db7d9 --- /dev/null +++ b/frontend/src/types/api/disks/getDisks.ts @@ -0,0 +1,4 @@ +export interface IDiskType { + name: string; + type: string; +} From 1b28a4e6f504a7f780bcd67508bf2e5d23e85a10 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Fri, 1 Apr 2022 15:43:58 +0530 Subject: [PATCH 010/154] chore: links are updated for all dashboard and promql (#908) --- frontend/src/container/ListOfDashboard/index.tsx | 2 +- .../container/NewWidget/LeftContainer/QuerySection/Query.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index 8828d748f2..492da5c846 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -157,7 +157,7 @@ function ListOfAllDashboard(): JSX.Element { diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/Query.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/Query.tsx index 97aa24d180..916771eece 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/Query.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/Query.tsx @@ -114,7 +114,7 @@ function Query({ From d085506d3e16f71f46de37d8289c1fd5b762495a Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Fri, 1 Apr 2022 15:47:39 +0530 Subject: [PATCH 011/154] bug: logged in check is added in the useEffect (#921) --- frontend/src/container/AppLayout/index.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 84f7d237e2..5a9051c215 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -27,6 +27,12 @@ function AppLayout(props: AppLayoutProps): JSX.Element { } }, [isLoggedIn, isSignUpPage]); + useEffect(() => { + if (isLoggedIn) { + history.push(ROUTES.APPLICATION); + } + }, [isLoggedIn]); + return ( {!isSignUpPage && } From 4dc668fd133dab6496047dd719cc30f79bd92ffb Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Fri, 1 Apr 2022 17:43:56 +0530 Subject: [PATCH 012/154] fix: remove unused props --- .../src/container/GridGraphLayout/Graph/FullView/index.tsx | 3 --- .../src/container/MetricsApplication/Tabs/Application.tsx | 2 -- frontend/src/container/MetricsApplication/Tabs/DBCall.tsx | 2 -- frontend/src/container/MetricsApplication/Tabs/External.tsx | 4 ---- 4 files changed, 11 deletions(-) diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index fcc656318c..ba8a66a82d 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -29,7 +29,6 @@ function FullView({ widget, fullViewOptions = true, onClickHandler, - noDataGraph = false, name, yAxisUnit, }: FullViewProps): JSX.Element { @@ -210,7 +209,6 @@ interface FullViewProps { widget: Widgets; fullViewOptions?: boolean; onClickHandler?: GraphOnClickHandler; - noDataGraph?: boolean; name: string; yAxisUnit?: string; } @@ -218,7 +216,6 @@ interface FullViewProps { FullView.defaultProps = { fullViewOptions: undefined, onClickHandler: undefined, - noDataGraph: undefined, yAxisUnit: undefined, }; diff --git a/frontend/src/container/MetricsApplication/Tabs/Application.tsx b/frontend/src/container/MetricsApplication/Tabs/Application.tsx index 774d8d7118..8cf4c2c3dd 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Application.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Application.tsx @@ -179,7 +179,6 @@ function Application({ getWidget }: DashboardProps): JSX.Element { { onClickhandler(event, element, chart, data, 'Request'); @@ -214,7 +213,6 @@ function Application({ getWidget }: DashboardProps): JSX.Element { { onClickhandler(ChartEvent, activeElements, chart, data, 'Error'); diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index 5b918338e4..20206c21d7 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -17,7 +17,6 @@ function DBCall({ getWidget }: DBCallProps): JSX.Element { External Call duration(by Address) Date: Fri, 1 Apr 2022 17:59:44 +0530 Subject: [PATCH 013/154] bug: no service and loading check are added (#934) --- .../src/modules/Servicemap/ServiceMap.tsx | 28 +++++------ frontend/src/store/actions/serviceMap.ts | 46 ++++++++++--------- frontend/src/store/actions/types.ts | 14 ++++-- frontend/src/store/reducers/serviceMap.ts | 7 +++ 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/frontend/src/modules/Servicemap/ServiceMap.tsx b/frontend/src/modules/Servicemap/ServiceMap.tsx index 9d07892fa5..7408fe206d 100644 --- a/frontend/src/modules/Servicemap/ServiceMap.tsx +++ b/frontend/src/modules/Servicemap/ServiceMap.tsx @@ -1,12 +1,13 @@ /* eslint-disable */ //@ts-nocheck +import { Card } from 'antd'; import Spinner from 'components/Spinner'; import React, { useEffect, useRef } from 'react'; import { ForceGraph2D } from 'react-force-graph'; import { connect } from 'react-redux'; import { RouteComponentProps, withRouter } from 'react-router-dom'; -import { getDetailedServiceMapItems, getServiceMapItems } from 'store/actions'; +import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions'; import { AppState } from 'store/reducers'; import styled from 'styled-components'; import { GlobalTime } from 'types/actions/globalTime'; @@ -31,9 +32,8 @@ const Container = styled.div` `; interface ServiceMapProps extends RouteComponentProps { - serviceMap: serviceMapStore; + serviceMap: ServiceMapStore; globalTime: GlobalTime; - getServiceMapItems: (time: GlobalTime) => void; getDetailedServiceMapItems: (time: GlobalTime) => void; } interface graphNode { @@ -53,29 +53,32 @@ export interface graphDataType { function ServiceMap(props: ServiceMapProps): JSX.Element { const fgRef = useRef(); - const { - getDetailedServiceMapItems, - getServiceMapItems, - globalTime, - serviceMap, - } = props; + const { getDetailedServiceMapItems, globalTime, serviceMap } = props; useEffect(() => { /* Call the apis only when the route is loaded. Check this issue: https://github.com/SigNoz/signoz/issues/110 */ - getServiceMapItems(globalTime); getDetailedServiceMapItems(globalTime); - }, [globalTime, getServiceMapItems, getDetailedServiceMapItems]); + }, [globalTime, getDetailedServiceMapItems]); useEffect(() => { fgRef.current && fgRef.current.d3Force('charge').strength(-400); }); - if (!serviceMap.items.length || !serviceMap.services.length) { + + if (serviceMap.loading) { return ; } + if (!serviceMap.loading && serviceMap.items.length === 0) { + return ( + + No Service Found + + ); + } + const zoomToService = (value: string): void => { fgRef && fgRef.current && @@ -149,7 +152,6 @@ const mapStateToProps = ( export default withRouter( connect(mapStateToProps, { - getServiceMapItems, getDetailedServiceMapItems, })(ServiceMap), ); diff --git a/frontend/src/store/actions/serviceMap.ts b/frontend/src/store/actions/serviceMap.ts index be5a84a239..a6f079a7eb 100644 --- a/frontend/src/store/actions/serviceMap.ts +++ b/frontend/src/store/actions/serviceMap.ts @@ -7,6 +7,7 @@ import { ActionTypes } from './types'; export interface ServiceMapStore { items: ServicesMapItem[]; services: ServicesItem[]; + loading: boolean; } export interface ServicesItem { @@ -37,38 +38,39 @@ export interface ServicesAction { payload: ServicesItem[]; } -export const getServiceMapItems = (globalTime: GlobalTime) => { - return async (dispatch: Dispatch): Promise => { - dispatch({ - type: ActionTypes.getServiceMapItems, - payload: [], - }); - - const requestString = `/serviceMapDependencies?start=${globalTime.minTime}&end=${globalTime.maxTime}`; - - const response = await api.get(requestString); - - dispatch({ - type: ActionTypes.getServiceMapItems, - payload: response.data, - }); +export interface ServiceMapLoading { + type: ActionTypes.serviceMapLoading; + payload: { + loading: ServiceMapStore['loading']; }; -}; +} export const getDetailedServiceMapItems = (globalTime: GlobalTime) => { return async (dispatch: Dispatch): Promise => { - dispatch({ - type: ActionTypes.getServices, - payload: [], - }); - const requestString = `/services?start=${globalTime.minTime}&end=${globalTime.maxTime}`; - const response = await api.get(requestString); + const serviceMapDependencies = `/serviceMapDependencies?start=${globalTime.minTime}&end=${globalTime.maxTime}`; + + const [serviceMapDependenciesResponse, response] = await Promise.all([ + api.get(serviceMapDependencies), + api.get(requestString), + ]); dispatch({ type: ActionTypes.getServices, payload: response.data, }); + + dispatch({ + type: ActionTypes.getServiceMapItems, + payload: serviceMapDependenciesResponse.data, + }); + + dispatch({ + type: ActionTypes.serviceMapLoading, + payload: { + loading: false, + }, + }); }; }; diff --git a/frontend/src/store/actions/types.ts b/frontend/src/store/actions/types.ts index c15ea00286..702997d49b 100644 --- a/frontend/src/store/actions/types.ts +++ b/frontend/src/store/actions/types.ts @@ -1,14 +1,22 @@ -import { ServiceMapItemAction, ServicesAction } from './serviceMap'; +import { + ServiceMapItemAction, + ServiceMapLoading, + ServicesAction, +} from './serviceMap'; import { GetUsageDataAction } from './usage'; export enum ActionTypes { - updateTraceFilters = 'UPDATE_TRACES_FILTER', updateTimeInterval = 'UPDATE_TIME_INTERVAL', getServiceMapItems = 'GET_SERVICE_MAP_ITEMS', getServices = 'GET_SERVICES', getUsageData = 'GET_USAGE_DATE', fetchTraces = 'FETCH_TRACES', fetchTraceItem = 'FETCH_TRACE_ITEM', + serviceMapLoading = 'UPDATE_SERVICE_MAP_LOADING', } -export type Action = GetUsageDataAction | ServicesAction | ServiceMapItemAction; +export type Action = + | GetUsageDataAction + | ServicesAction + | ServiceMapItemAction + | ServiceMapLoading; diff --git a/frontend/src/store/reducers/serviceMap.ts b/frontend/src/store/reducers/serviceMap.ts index ef7cd21496..18ec21a9ec 100644 --- a/frontend/src/store/reducers/serviceMap.ts +++ b/frontend/src/store/reducers/serviceMap.ts @@ -3,6 +3,7 @@ import { Action, ActionTypes, ServiceMapStore } from 'store/actions'; const initialState: ServiceMapStore = { items: [], services: [], + loading: true, }; export const ServiceMapReducer = ( @@ -20,6 +21,12 @@ export const ServiceMapReducer = ( ...state, services: action.payload, }; + case ActionTypes.serviceMapLoading: { + return { + ...state, + loading: action.payload.loading, + }; + } default: return state; } From 53e7037f48ec4d8abf8ac33a1b15c5996d7bb5bd Mon Sep 17 00:00:00 2001 From: Naman Jain Date: Sat, 2 Apr 2022 16:15:03 +0530 Subject: [PATCH 014/154] fix: run go vet to fix some issues with json tag (#936) Co-authored-by: Naman Jain --- pkg/query-service/godruid/queries.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/godruid/queries.go b/pkg/query-service/godruid/queries.go index 5fb518f908..ffdeee002d 100644 --- a/pkg/query-service/godruid/queries.go +++ b/pkg/query-service/godruid/queries.go @@ -62,7 +62,7 @@ type QueryScan struct { Limit int64 `json:"limit,omitempty"` Offset int64 `json:"offset,omitempty"` BatchSize int64 `json:"batchSize,omitempty"` - Order string `json:"order",omitempty` + Order string `json:"order,omitempty"` ResultFormat string `json:"resultFormat"` Context map[string]interface{} `json:"context,omitempty"` @@ -189,7 +189,7 @@ type TimeBoundaryItem struct { type TimeBoundary struct { MinTime string `json:"minTime"` - MaxTime string `json:"minTime"` + MaxTime string `json:"maxTime"` } func (q *QueryTimeBoundary) setup() { q.QueryType = "timeBoundary" } From 32e8e489280cb7832ef7532d87febc8bc2555497 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Mon, 4 Apr 2022 08:24:28 +0530 Subject: [PATCH 015/154] chore: behaviour for dropdown is updated --- .../AllTags/Tag/DebounceSelect/index.tsx | 63 ----------------- .../Trace/Search/AllTags/Tag/TagValue.tsx | 69 +++++++++++++++++++ .../Trace/Search/AllTags/Tag/config.ts | 28 -------- .../Trace/Search/AllTags/Tag/index.tsx | 38 ++++------ 4 files changed, 81 insertions(+), 117 deletions(-) delete mode 100644 frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx create mode 100644 frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx delete mode 100644 frontend/src/container/Trace/Search/AllTags/Tag/config.ts diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx deleted file mode 100644 index 2576e782e0..0000000000 --- a/frontend/src/container/Trace/Search/AllTags/Tag/DebounceSelect/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Select, Spin } from 'antd'; -import { SelectProps } from 'antd/es/select'; -import debounce from 'lodash-es/debounce'; -import React, { useRef, useState } from 'react'; - -export interface DebounceSelectProps - extends Omit, 'options' | 'children'> { - fetchOptions: (search: string) => Promise; - debounceTimeout: number; -} - -function DebounceSelect< - ValueType extends { - key?: string; - label: React.ReactNode; - value: string | number; - } = never ->({ - fetchOptions, - debounceTimeout = 800, - ...props -}: DebounceSelectProps): JSX.Element { - const [fetching, setFetching] = useState(false); - const [options, setOptions] = useState([]); - const fetchRef = useRef(0); - - const debounceFetcher = React.useMemo(() => { - const loadOptions = (value: string): void => { - fetchRef.current += 1; - const fetchId = fetchRef.current; - setOptions([]); - setFetching(true); - - fetchOptions(value).then((newOptions) => { - if (fetchId !== fetchRef.current) { - // for fetch callback order - return; - } - - setOptions(newOptions); - setFetching(false); - }); - }; - - return debounce(loadOptions, debounceTimeout); - }, [fetchOptions, debounceTimeout]); - - return ( - - labelInValue - filterOption={false} - onSearch={debounceFetcher} - notFoundContent={fetching ? : null} - style={{ width: '170px' }} - // as all other props are from SelectProps only - // eslint-disable-next-line react/jsx-props-no-spreading - {...props} - options={options} - /> - ); -} - -export default DebounceSelect; diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx new file mode 100644 index 0000000000..378001bec4 --- /dev/null +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx @@ -0,0 +1,69 @@ +import { Select } from 'antd'; +import { DefaultOptionType } from 'antd/lib/select'; +import getTagValue from 'api/trace/getTagValue'; +import useFetch from 'hooks/useFetch'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { PayloadProps, Props } from 'types/api/trace/getTagValue'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { TraceReducer } from 'types/reducer/trace'; + +import { Value } from '.'; +import { SelectComponent } from './styles'; + +function TagValue(props: TagValueProps): JSX.Element { + const { tag, setLocalSelectedTags, index, tagKey } = props; + const { + Key: selectedKey, + Operator: selectedOperator, + Values: selectedValues, + } = tag; + + const globalReducer = useSelector( + (state) => state.globalTime, + ); + + const valueSuggestion = useFetch(getTagValue, { + end: globalReducer.maxTime, + start: globalReducer.minTime, + tagKey, + }); + + return ( + { + if (typeof value === 'string') { + setLocalSelectedTags((tags) => [ + ...tags.slice(0, index), + { + Key: selectedKey, + Operator: selectedOperator, + Values: [...selectedValues, value], + }, + ...tags.slice(index + 1, tags.length), + ]); + } + }} + loading={valueSuggestion.loading || false} + > + {valueSuggestion.payload && + valueSuggestion.payload.map((suggestion) => ( + + {suggestion.tagValues} + + ))} + + ); +} + +interface TagValueProps { + index: number; + tag: FlatArray; + setLocalSelectedTags: React.Dispatch< + React.SetStateAction + >; + tagKey: string; +} + +export default TagValue; diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/config.ts b/frontend/src/container/Trace/Search/AllTags/Tag/config.ts deleted file mode 100644 index ce414a5f15..0000000000 --- a/frontend/src/container/Trace/Search/AllTags/Tag/config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import getTagValue from 'api/trace/getTagValue'; - -// Usage of DebounceSelect -export interface TagValue { - label: string; - value: string; -} - -export async function fetchTag( - globalStart: number, - globalEnd: number, - tagKey: string, -): Promise { - const response = await getTagValue({ - end: globalEnd, - start: globalStart, - tagKey, - }); - - if (response.statusCode !== 200 || !response.payload) { - return []; - } - - return response.payload.map((e) => ({ - label: e.tagValues, - value: e.tagValues, - })); -} diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx index 37b42347a1..ad9b0e7972 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx @@ -3,13 +3,11 @@ import { Select } from 'antd'; import React from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import { GlobalReducer } from 'types/reducer/globalTime'; import { TraceReducer } from 'types/reducer/trace'; -import { fetchTag, TagValue } from './config'; -import DebounceSelect from './DebounceSelect'; import { Container, IconContainer, SelectComponent } from './styles'; import TagsKey from './TagKey'; +import TagValue from './TagValue'; const { Option } = Select; @@ -33,9 +31,6 @@ const AllMenu: AllMenuProps[] = [ function SingleTags(props: AllTagsProps): JSX.Element { const traces = useSelector((state) => state.traces); - const globalReducer = useSelector( - (state) => state.globalTime, - ); const { tag, onCloseHandler, setLocalSelectedTags, index } = props; const { @@ -69,7 +64,6 @@ function SingleTags(props: AllTagsProps): JSX.Element { tag={tag} setLocalSelectedTags={setLocalSelectedTags} /> - e.key === selectedOperator)?.value || ''} @@ -81,24 +75,16 @@ function SingleTags(props: AllTagsProps): JSX.Element { ))} - => - fetchTag(globalReducer.minTime, globalReducer.maxTime, selectedKey[index]) - } - debounceTimeout={300} - onSelect={(value: Value): void => { - setLocalSelectedTags((tags) => [ - ...tags.slice(0, index), - { - Key: selectedKey, - Operator: selectedOperator, - Values: [...selectedValues, value.value], - }, - ...tags.slice(index + 1, tags.length), - ]); - }} - mode="multiple" - /> + {selectedKey[0] ? ( + + ) : ( + + )} onDeleteTagHandler(index)}> @@ -116,7 +102,7 @@ interface AllTagsProps { >; } -interface Value { +export interface Value { key: string; label: string; value: string; From 6c4c814b3ff79e137803f629d81a5e560265e48d Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Mon, 4 Apr 2022 10:25:15 +0530 Subject: [PATCH 016/154] bug: pathname check is added (#948) --- frontend/src/container/AppLayout/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 5a9051c215..5230ca5bae 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -28,10 +28,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element { }, [isLoggedIn, isSignUpPage]); useEffect(() => { - if (isLoggedIn) { + if (isLoggedIn && pathname === ROUTES.SIGN_UP) { history.push(ROUTES.APPLICATION); } - }, [isLoggedIn]); + }, [isLoggedIn, pathname]); return ( From 8064ae1f37679b521aad3d84c33c7e6005605df5 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Mon, 4 Apr 2022 15:06:06 +0530 Subject: [PATCH 017/154] feat: ttl api integration --- frontend/src/api/settings/setRetention.ts | 6 +- .../container/GeneralSettings/Retention.tsx | 6 +- .../src/container/GeneralSettings/index.tsx | 198 ++++++++---------- .../src/types/api/settings/getRetention.ts | 2 + .../src/types/api/settings/setRetention.ts | 6 +- 5 files changed, 98 insertions(+), 120 deletions(-) diff --git a/frontend/src/api/settings/setRetention.ts b/frontend/src/api/settings/setRetention.ts index dcf0a0f2c3..da8fdf515b 100644 --- a/frontend/src/api/settings/setRetention.ts +++ b/frontend/src/api/settings/setRetention.ts @@ -8,9 +8,9 @@ const setRetention = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.post( - `/settings/ttl?duration=${props.duration}&type=${props.type}`, - ); + const response = await axios.post(`/settings/ttl`, { + props, + }); return { statusCode: 200, diff --git a/frontend/src/container/GeneralSettings/Retention.tsx b/frontend/src/container/GeneralSettings/Retention.tsx index 64591b9b59..653597c3f7 100644 --- a/frontend/src/container/GeneralSettings/Retention.tsx +++ b/frontend/src/container/GeneralSettings/Retention.tsx @@ -17,6 +17,7 @@ function Retention({ retentionValue, setRetentionValue, text, + hide, }: RetentionProps): JSX.Element { const { value: initialValue, @@ -64,7 +65,9 @@ function Retention({ func(null); } }; - + if (hide) { + return null; + } return ( @@ -103,6 +106,7 @@ interface RetentionProps { retentionValue: number; text: string; setRetentionValue: React.Dispatch>; + hide: boolean; } export default Retention; diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index 7796e38d5f..db2c4f3ec8 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -1,13 +1,15 @@ import { Button, Col, Modal, notification, Row, Typography } from 'antd'; import getDisks from 'api/disks/getDisks'; -import getRetentionperoidApi from 'api/settings/getRetention'; +import getRetentionPeriodApi from 'api/settings/getRetention'; import setRetentionApi from 'api/settings/setRetention'; import Spinner from 'components/Spinner'; import TextToolTip from 'components/TextToolTip'; import useFetch from 'hooks/useFetch'; import convertIntoHr from 'lib/convertIntoHr'; import getSettingsPeroid from 'lib/getSettingsPeroid'; -import React, { useCallback, useEffect, useState } from 'react'; +import { find } from 'lodash-es'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { IDiskType } from 'types/api/disks/getDisks'; import { PayloadProps } from 'types/api/settings/getRetention'; import Retention from './Retention'; @@ -24,43 +26,36 @@ function GeneralSettings(): JSX.Element { setSelectedMetricsPeroid, ] = useState('month'); const [notifications, Element] = notification.useNotification(); - const [retentionPeroidMetrics, setRetentionPeroidMetrics] = useState( - '', - ); + const [modal, setModal] = useState(false); const [postApiLoading, setPostApiLoading] = useState(false); - const [selectedTracePeroid, setSelectedTracePeroid] = useState( - 'hr', - ); - - const [retentionPeroidTrace, setRetentionPeroidTrace] = useState(''); const [isDefaultMetrics, setIsDefaultMetrics] = useState(false); const [isDefaultTrace, setIsDefaultTrace] = useState(false); - const [availableDisks, setAvailableDisks] = useState(null); + const [availableDisks, setAvailableDisks] = useState(null); useEffect(() => { - getDisks().then((resp) => console.log({ disks: resp })) - }, []) - const currentTTLValues = { - metrics_ttl_duration_hrs: 24 * 30 * 10, - metrics_move_ttl_duration_hrs: -1, - traces_ttl_duration_hrs: -1, - traces_move_ttl_duration_hrs: -1, - }; + getDisks().then((response) => setAvailableDisks(response)); + }, []); + + const { payload: currentTTLValues, loading, error, errorMessage } = useFetch< + PayloadProps, + undefined + >(getRetentionPeriodApi, undefined); + const [metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod] = useState< number | null - >(currentTTLValues.metrics_ttl_duration_hrs); + >(currentTTLValues?.metrics_ttl_duration_hrs); const [metricsS3RetentionPeriod, setMetricsS3RetentionPeriod] = useState< number | null - >(currentTTLValues.metrics_move_ttl_duration_hrs); + >(currentTTLValues?.metrics_move_ttl_duration_hrs); const [tracesTotalRetentionPeriod, setTracesTotalRetentionPeriod] = useState< number | null - >(currentTTLValues.traces_ttl_duration_hrs); + >(currentTTLValues?.traces_ttl_duration_hrs); const [tracesS3RetentionPeriod, setTracesS3RetentionPeriod] = useState< number | null - >(currentTTLValues.traces_move_ttl_duration_hrs); + >(currentTTLValues?.traces_move_ttl_duration_hrs); const onModalToggleHandler = (): void => { setModal((modal) => !modal); @@ -70,11 +65,6 @@ function GeneralSettings(): JSX.Element { onModalToggleHandler(); }, []); - const { payload, loading, error, errorMessage } = useFetch< - PayloadProps, - undefined - >(getRetentionperoidApi, undefined); - const checkMetricTraceDefault = (trace: number, metric: number): void => { if (metric === -1) { setIsDefaultMetrics(true); @@ -89,99 +79,78 @@ function GeneralSettings(): JSX.Element { } }; // const retentionRenderConfig = () => { }; - const renderConfig = [ - { - name: 'Metrics', - retentionFields: [ - { - name: 'Total Retention Period', - value: metricsTotalRetentionPeriod, - setValue: setMetricsTotalRetentionPeriod, - }, - { - name: `Move to S3\n(should be lower than total retention period)`, - value: metricsS3RetentionPeriod, - setValue: setMetricsS3RetentionPeriod, - }, - ], - }, - { - name: 'Traces', - retentionFields: [ - { - name: 'Total Retention Period', - value: tracesTotalRetentionPeriod, - setValue: setTracesTotalRetentionPeriod, - }, - { - name: `Move to S3\n(should be lower than total retention period)`, - value: tracesS3RetentionPeriod, - setValue: setTracesS3RetentionPeriod, - }, - ], - }, - ]; - - useEffect(() => { - if (!loading && payload !== undefined) { - const { - metrics_ttl_duration_hrs: metricTllDuration, - traces_ttl_duration_hrs: traceTllDuration, - } = payload; - - checkMetricTraceDefault(traceTllDuration, metricTllDuration); - - const traceValue = getSettingsPeroid(traceTllDuration); - const metricsValue = getSettingsPeroid(metricTllDuration); - - setRetentionPeroidTrace(traceValue.value.toString()); - setSelectedTracePeroid(traceValue.peroid); - - setRetentionPeroidMetrics(metricsValue.value.toString()); - setSelectedMetricsPeroid(metricsValue.peroid); - } - }, [setSelectedMetricsPeroid, loading, payload]); + const renderConfig = useMemo(() => { + const s3Enabled = !!find( + availableDisks, + (disks: IDiskType) => disks?.type === 's3', + ); + return [ + { + name: 'Metrics', + retentionFields: [ + { + name: 'Total Retention Period', + value: metricsTotalRetentionPeriod, + setValue: setMetricsTotalRetentionPeriod, + }, + { + name: `Move to S3\n(should be lower than total retention period)`, + value: metricsS3RetentionPeriod, + setValue: setMetricsS3RetentionPeriod, + hide: !s3Enabled, + }, + ], + }, + { + name: 'Traces', + retentionFields: [ + { + name: 'Total Retention Period', + value: tracesTotalRetentionPeriod, + setValue: setTracesTotalRetentionPeriod, + }, + { + name: `Move to S3\n(should be lower than total retention period)`, + value: tracesS3RetentionPeriod, + setValue: setTracesS3RetentionPeriod, + hide: !s3Enabled, + }, + ], + }, + ]; + }, [ + availableDisks, + metricsS3RetentionPeriod, + metricsTotalRetentionPeriod, + tracesS3RetentionPeriod, + tracesTotalRetentionPeriod, + ]); const onOkHandler = async (): Promise => { try { setPostApiLoading(true); - const retentionTraceValue = - retentionPeroidTrace === '0' && (payload?.traces_ttl_duration_hrs || 0) < 0 - ? payload?.traces_ttl_duration_hrs || 0 - : parseInt(retentionPeroidTrace, 10); - - const retentionMetricsValue = - retentionPeroidMetrics === '0' && - (payload?.metrics_ttl_duration_hrs || 0) < 0 - ? payload?.metrics_ttl_duration_hrs || 0 - : parseInt(retentionPeroidMetrics, 10); - - const [tracesResponse, metricsResponse] = await Promise.all([ - setRetentionApi({ - duration: `${convertIntoHr(retentionTraceValue, selectedTracePeroid)}h`, - type: 'traces', - }), - setRetentionApi({ - duration: `${convertIntoHr( - retentionMetricsValue, - selectedMetricsPeroid, - )}h`, - type: 'metrics', - }), - ]); - - if ( - tracesResponse.statusCode === 200 && - metricsResponse.statusCode === 200 - ) { + // const retentionTraceValue = + // retentionPeroidTrace === '0' && (payload?.traces_ttl_duration_hrs || 0) < 0 + // ? payload?.traces_ttl_duration_hrs || 0 + // : parseInt(retentionPeroidTrace, 10); + // const retentionMetricsValue = + // retentionPeroidMetrics === '0' && + // (payload?.metrics_ttl_duration_hrs || 0) < 0 + // ? payload?.metrics_ttl_duration_hrs || 0 + // : parseInt(retentionPeroidMetrics, 10); + const apiResponse = await setRetentionApi({ + metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, + metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, + traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, + traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, + }); + if (apiResponse.statusCode === 200) { notifications.success({ message: 'Success!', placement: 'topRight', description: 'Congrats. The retention periods were updated correctly.', }); - - checkMetricTraceDefault(retentionTraceValue, retentionMetricsValue); - + // checkMetricTraceDefault(retentionTraceValue, retentionMetricsValue); onModalToggleHandler(); } else { notifications.error({ @@ -206,7 +175,7 @@ function GeneralSettings(): JSX.Element { return {errorMessage}; } - if (loading || payload === undefined) { + if (loading || currentTTLValues === undefined) { return ; } @@ -230,7 +199,7 @@ function GeneralSettings(): JSX.Element { }; const isDisabledHandler = (): boolean => { - return !!(retentionPeroidTrace === '' || retentionPeroidMetrics === ''); + return false; }; const errorText = getErrorText(); @@ -275,6 +244,7 @@ function GeneralSettings(): JSX.Element { text={retentionField.name} retentionValue={retentionField.value} setRetentionValue={retentionField.setValue} + hide={!!retentionField.hide} /> ))} diff --git a/frontend/src/types/api/settings/getRetention.ts b/frontend/src/types/api/settings/getRetention.ts index e6fd1eae43..f3ba539416 100644 --- a/frontend/src/types/api/settings/getRetention.ts +++ b/frontend/src/types/api/settings/getRetention.ts @@ -1,4 +1,6 @@ export interface PayloadProps { metrics_ttl_duration_hrs: number; + metrics_move_ttl_duration_hrs: number; traces_ttl_duration_hrs: number; + traces_move_ttl_duration_hrs: number; } diff --git a/frontend/src/types/api/settings/setRetention.ts b/frontend/src/types/api/settings/setRetention.ts index 6e82f8ef2a..9c2ced48f8 100644 --- a/frontend/src/types/api/settings/setRetention.ts +++ b/frontend/src/types/api/settings/setRetention.ts @@ -1,6 +1,8 @@ export interface Props { - type: 'metrics' | 'traces'; - duration: string; + metrics_ttl_duration_hrs: number; + metrics_move_ttl_duration_hrs: number; + traces_ttl_duration_hrs: number; + traces_move_ttl_duration_hrs: number; } export interface PayloadProps { From 5be1eb58b22962aadbb303349e918696470c002d Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Mon, 4 Apr 2022 15:26:29 +0530 Subject: [PATCH 018/154] feat: ttl api integration --- frontend/src/api/settings/setRetention.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/api/settings/setRetention.ts b/frontend/src/api/settings/setRetention.ts index da8fdf515b..6cb8c436a2 100644 --- a/frontend/src/api/settings/setRetention.ts +++ b/frontend/src/api/settings/setRetention.ts @@ -8,9 +8,7 @@ const setRetention = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.post(`/settings/ttl`, { - props, - }); + const response = await axios.post(`/settings/ttl`, props); return { statusCode: 200, From fd83cea9a032e17b9a02c790b30bdfb51a927b8e Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Mon, 4 Apr 2022 17:07:21 +0530 Subject: [PATCH 019/154] chore: removing version api from being tracked (#950) --- pkg/query-service/telemetry/ignoredPaths.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/query-service/telemetry/ignoredPaths.go b/pkg/query-service/telemetry/ignoredPaths.go index 3fc2f1898a..e6adf197ff 100644 --- a/pkg/query-service/telemetry/ignoredPaths.go +++ b/pkg/query-service/telemetry/ignoredPaths.go @@ -2,7 +2,8 @@ package telemetry func IgnoredPaths() map[string]struct{} { ignoredPaths := map[string]struct{}{ - "/api/v1/tags": struct{}{}, + "/api/v1/tags": struct{}{}, + "/api/v1/version": struct{}{}, } return ignoredPaths From a0efa63185900baa7c3a60cbdb13be20e2924ee3 Mon Sep 17 00:00:00 2001 From: Nishidh Jain <61869195+NishidhJain@users.noreply.github.com> Date: Mon, 4 Apr 2022 17:35:44 +0530 Subject: [PATCH 020/154] Fix(FE) : Ask for confirmation before deleting any dashboard from dashboard list (#534) * A confirmation dialog will pop up before deleting any dashboard Co-authored-by: Palash gupta --- .../TableComponents/DeleteButton.tsx | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx index f0b2d183df..fd9ef16001 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx @@ -1,4 +1,6 @@ -import React, { useCallback } from 'react'; +import { ExclamationCircleOutlined } from '@ant-design/icons'; +import { Modal } from 'antd'; +import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; @@ -8,14 +10,29 @@ import AppActions from 'types/actions'; import { Data } from '../index'; import { TableLinkText } from './styles'; -function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element { - const onClickHandler = useCallback(() => { - deleteDashboard({ - uuid: id, - }); - }, [id, deleteDashboard]); +const { confirm } = Modal; - return Delete; +function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element { + const openConfirmationDialog = (): void => { + confirm({ + title: 'Do you really want to delete this dashboard?', + icon: , + onOk() { + deleteDashboard({ + uuid: id, + }); + }, + okText: 'Delete', + okButtonProps: { danger: true }, + centered: true, + }); + }; + + return ( + + Delete + + ); } interface DispatchProps { From 24d6a1e7b2133354a475cbfa5252ad745d609b4f Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Mon, 4 Apr 2022 19:38:23 +0530 Subject: [PATCH 021/154] feat: s3 ttl validation --- frontend/src/api/settings/setRetention.ts | 8 +- .../container/GeneralSettings/Retention.tsx | 19 +- .../src/container/GeneralSettings/index.tsx | 277 +++++++++++------- .../src/types/api/settings/setRetention.ts | 8 +- 4 files changed, 195 insertions(+), 117 deletions(-) diff --git a/frontend/src/api/settings/setRetention.ts b/frontend/src/api/settings/setRetention.ts index 6cb8c436a2..62aa3559e5 100644 --- a/frontend/src/api/settings/setRetention.ts +++ b/frontend/src/api/settings/setRetention.ts @@ -8,7 +8,13 @@ const setRetention = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.post(`/settings/ttl`, props); + const response = await axios.post( + `/settings/ttl?duration=${props.totalDuration}&type=${props.type}${ + props.coldStorage + ? `&coldStorage=${props.coldStorage};toColdDuration=${props.toColdDuration}` + : '' + }`, + ); return { statusCode: 200, diff --git a/frontend/src/container/GeneralSettings/Retention.tsx b/frontend/src/container/GeneralSettings/Retention.tsx index 653597c3f7..e6cef0383d 100644 --- a/frontend/src/container/GeneralSettings/Retention.tsx +++ b/frontend/src/container/GeneralSettings/Retention.tsx @@ -24,9 +24,17 @@ function Retention({ timeUnitValue: initialTimeUnitValue, } = convertHoursValueToRelevantUnit(retentionValue); const [selectedTimeUnit, setSelectTimeUnit] = useState(initialTimeUnitValue); - const [selectedValue, setSelectedValue] = useState( - initialValue, - ); + const [selectedValue, setSelectedValue] = useState(null); + + useEffect(() => { + setSelectedValue(initialValue); + }, [initialValue]); + + + useEffect(() => { + setSelectTimeUnit(initialTimeUnitValue); + }, [initialTimeUnitValue]); + const menuItems = TimeUnits.map((option) => ( - {category.name} - - {category.retentionFields.map((retentionField) => ( - - ))} - - ); - } - return null; - })} */} - {renderConfig} - + {renderConfig} { - for (let idx = TimeUnits.length - 1; idx >= 0; idx -= 1) { - const timeUnit = TimeUnits[idx]; - const convertedValue = timeUnit.multiplier * value; + if (value) + for (let idx = TimeUnits.length - 1; idx >= 0; idx -= 1) { + const timeUnit = TimeUnits[idx]; + const convertedValue = timeUnit.multiplier * value; - if ( - convertedValue >= 1 && - convertedValue === parseInt(`${convertedValue}`, 10) - ) { - return { value: convertedValue, timeUnitValue: timeUnit.value }; + if ( + convertedValue >= 1 && + convertedValue === parseInt(`${convertedValue}`, 10) + ) { + return { value: convertedValue, timeUnitValue: timeUnit.value }; + } } - } return { value, timeUnitValue: TimeUnits[0].value }; }; diff --git a/frontend/src/pages/AlertChannelCreate/index.tsx b/frontend/src/pages/AlertChannelCreate/index.tsx index 1f316fe860..3de6ce9986 100644 --- a/frontend/src/pages/AlertChannelCreate/index.tsx +++ b/frontend/src/pages/AlertChannelCreate/index.tsx @@ -26,8 +26,7 @@ function SettingsPage(): JSX.Element { route: ROUTES.ALL_CHANNELS, }, ], - activeKey: - pathName === ROUTES.SETTINGS ? 'General' : 'Alert Channels', + activeKey: pathName === ROUTES.SETTINGS ? 'General' : 'Alert Channels', }} /> ); diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx index 2b23cec2ec..208fcde7c1 100644 --- a/frontend/src/pages/Settings/index.tsx +++ b/frontend/src/pages/Settings/index.tsx @@ -23,8 +23,7 @@ function SettingsPage(): JSX.Element { route: ROUTES.ALL_CHANNELS, }, ], - activeKey: - pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General', + activeKey: pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General', }} /> ); From 147ed9f24b181f6f5123cb76d7d4a82a386ab772 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Tue, 5 Apr 2022 11:19:06 +0530 Subject: [PATCH 023/154] chore: editor config is added (#818) --- .editorconfig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..81ed7f265d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,33 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation +[*.py] +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +# Indentation override for all JS under lib directory +[lib/**.js] +indent_style = space +indent_size = 2 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 \ No newline at end of file From 76fccbbba49820d995f3277603fefaa96c1b7c9b Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 12:19:54 +0530 Subject: [PATCH 024/154] fix: styling changes --- .../container/GeneralSettings/Retention.tsx | 24 +++++++++---------- .../src/container/GeneralSettings/styles.ts | 9 +++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/frontend/src/container/GeneralSettings/Retention.tsx b/frontend/src/container/GeneralSettings/Retention.tsx index 03af1aeb5e..54fb89a394 100644 --- a/frontend/src/container/GeneralSettings/Retention.tsx +++ b/frontend/src/container/GeneralSettings/Retention.tsx @@ -2,7 +2,12 @@ import { Col, Row, Select } from 'antd'; import { find } from 'lodash-es'; import React, { useEffect, useRef, useState } from 'react'; -import { Input, RetentionContainer, Typography } from './styles'; +import { + Input, + RetentionContainer, + RetentionFieldInputContainer, + RetentionFieldLabel, +} from './styles'; import { convertHoursValueToRelevantUnit, SettingPeriod, @@ -22,7 +27,9 @@ function Retention({ timeUnitValue: initialTimeUnitValue, } = convertHoursValueToRelevantUnit(Number(retentionValue)); const [selectedTimeUnit, setSelectTimeUnit] = useState(initialTimeUnitValue); - const [selectedValue, setSelectedValue] = useState(null); + const [selectedValue, setSelectedValue] = useState( + initialValue, + ); const interacted = useRef(false); useEffect(() => { if (!interacted.current) setSelectedValue(initialValue); @@ -78,17 +85,10 @@ function Retention({ - - {text} - + {text}-
+ = 0 ? selectedValue : ''} onChange={(e): void => onChangeHandler(e, setSelectedValue)} @@ -101,7 +101,7 @@ function Retention({ > {menuItems} -
+ diff --git a/frontend/src/container/GeneralSettings/styles.ts b/frontend/src/container/GeneralSettings/styles.ts index 417fdb894e..a9f8b0fc57 100644 --- a/frontend/src/container/GeneralSettings/styles.ts +++ b/frontend/src/container/GeneralSettings/styles.ts @@ -80,3 +80,12 @@ export const ErrorText = styled(TypographyComponent)` font-style: italic; } `; + +export const RetentionFieldLabel = styled(TypographyComponent)` + vertical-align: middle; + white-space: pre-wrap; +`; + +export const RetentionFieldInputContainer = styled.div` + display: inline-flex; +`; From 170609a81f1f7ce2a05fc4bc68be3c558e0c4b04 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 12:26:43 +0530 Subject: [PATCH 025/154] chore: type changes --- frontend/src/container/GeneralSettings/index.tsx | 12 ++++++++++-- frontend/src/types/api/settings/getRetention.ts | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index b72a7e0320..a38d7e6c81 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -50,9 +50,17 @@ function GeneralSettings(): JSX.Element { useEffect(() => { if (currentTTLValues) { setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs); - setMetricsS3RetentionPeriod(currentTTLValues.metrics_move_ttl_duration_hrs); + setMetricsS3RetentionPeriod( + currentTTLValues.metrics_move_ttl_duration_hrs + ? currentTTLValues.metrics_move_ttl_duration_hrs + : null, + ); setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs); - setTracesS3RetentionPeriod(currentTTLValues.traces_move_ttl_duration_hrs); + setTracesS3RetentionPeriod( + currentTTLValues.traces_move_ttl_duration_hrs + ? currentTTLValues.traces_move_ttl_duration_hrs + : null, + ); } }, [currentTTLValues]); diff --git a/frontend/src/types/api/settings/getRetention.ts b/frontend/src/types/api/settings/getRetention.ts index f3ba539416..f8a2f8b1bc 100644 --- a/frontend/src/types/api/settings/getRetention.ts +++ b/frontend/src/types/api/settings/getRetention.ts @@ -1,6 +1,6 @@ export interface PayloadProps { metrics_ttl_duration_hrs: number; - metrics_move_ttl_duration_hrs: number; + metrics_move_ttl_duration_hrs?: number; traces_ttl_duration_hrs: number; - traces_move_ttl_duration_hrs: number; + traces_move_ttl_duration_hrs?: number; } From 3f2a4d6eac10f30db5cf5164c38946dad2dfad21 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Tue, 5 Apr 2022 13:23:08 +0530 Subject: [PATCH 026/154] bug: Trace filter page fixes (#846) * order is added in the url * local min max duration is kept in memory to show min and max even after filtering by duration * checkbox ordering does not change when the user selects or un-selects a checkbox --- .../Panel/PanelBody/Common/Checkbox.tsx | 16 ++-- .../Panel/PanelBody/CommonCheckBox/index.tsx | 30 ++++--- .../Panel/PanelBody/Duration/index.tsx | 79 +++++++++------- .../Filters/Panel/PanelHeading/index.tsx | 90 +++++++++++-------- .../Filters/Panel/PanelHeading/styles.ts | 2 +- .../container/Trace/Search/AllTags/index.tsx | 12 +-- frontend/src/container/Trace/Search/index.tsx | 18 ++-- .../src/container/Trace/TraceTable/index.tsx | 72 ++++++++------- frontend/src/pages/Trace/index.tsx | 5 +- .../store/actions/trace/getInitialFilter.ts | 7 ++ .../actions/trace/getInitialSpansAggregate.ts | 31 +++++-- .../store/actions/trace/parseFilter/index.ts | 3 +- ...current.ts => spanAggregateCurrentPage.ts} | 2 +- .../trace/parseFilter/spanAggregateOrder.ts | 39 ++++++++ .../store/actions/trace/selectTraceFilter.ts | 1 + .../actions/trace/updateTagPanelVisiblity.ts | 6 +- frontend/src/store/actions/trace/util.ts | 10 ++- frontend/src/store/reducers/trace.ts | 22 ++++- frontend/src/types/actions/trace.ts | 25 ++++-- .../src/types/api/trace/getSpanAggregate.ts | 2 +- frontend/src/types/reducer/trace.ts | 3 + 21 files changed, 308 insertions(+), 167 deletions(-) rename frontend/src/store/actions/trace/parseFilter/{current.ts => spanAggregateCurrentPage.ts} (93%) create mode 100644 frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx index e177f1b4b3..3ac230f669 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx @@ -93,11 +93,15 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { if (response.statusCode === 200) { const updatedFilter = getFilter(response.payload); - updatedFilter.forEach((value, key) => { - if (key !== 'duration' && name !== key) { - preUserSelectedMap.set(key, Object.keys(value)); - } - }); + // updatedFilter.forEach((value, key) => { + // if (key !== 'duration' && name !== key) { + // preUserSelectedMap.set(key, Object.keys(value)); + // } + + // if (key === 'duration') { + // newSelectedMap.set('duration', [value.maxDuration, value.minDuration]); + // } + // }); updatedFilter.set(name, { [`${keyValue}`]: '-1', @@ -115,6 +119,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { selectedFilter: newSelectedMap, userSelected: preUserSelectedMap, isFilterExclude: preIsFilterExclude, + order: spansAggregate.order, }, }); @@ -128,6 +133,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { updatedFilter, preIsFilterExclude, preUserSelectedMap, + spansAggregate.order, ); } else { setIsLoading(false); diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx index 161bb6f8ac..0681e7e5d5 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx @@ -18,16 +18,26 @@ function CommonCheckBox(props: CommonCheckBoxProps): JSX.Element { return ( <> - {statusObj.map((e) => ( - - ))} + {statusObj + .sort((a, b) => { + const countA = +status[a]; + const countB = +status[b]; + + if (countA === countB) { + return a.length - b.length; + } + return countA - countB; + }) + .map((e) => ( + + ))} ); } diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx index ef61e3a6a5..8453e5ca90 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx @@ -5,7 +5,7 @@ import getFilters from 'api/trace/getFilters'; import dayjs from 'dayjs'; import durationPlugin from 'dayjs/plugin/duration'; import useDebouncedFn from 'hooks/useDebouncedFunction'; -import React, { useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Dispatch } from 'redux'; import { getFilter, updateURL } from 'store/actions/trace/util'; @@ -20,11 +20,13 @@ import { Container, InputContainer, Text } from './styles'; dayjs.extend(durationPlugin); const getMs = (value: string): string => { - return dayjs - .duration({ - milliseconds: parseInt(value, 10) / 1000000, - }) - .format('SSS'); + return parseFloat( + dayjs + .duration({ + milliseconds: parseInt(value, 10) / 1000000, + }) + .format('SSS'), + ).toFixed(2); }; function Duration(): JSX.Element { @@ -43,9 +45,10 @@ function Duration(): JSX.Element { (state) => state.globalTime, ); - const getDuration = (): - | { maxDuration: string; minDuration: string } - | Record => { + const preLocalMaxDuration = useRef(); + const preLocalMinDuration = useRef(); + + const getDuration = useMemo(() => { const selectedDuration = selectedFilter.get('duration'); if (selectedDuration) { @@ -56,17 +59,29 @@ function Duration(): JSX.Element { } return filter.get('duration') || {}; - }; + }, [selectedFilter, filter]); - const duration = getDuration(); + const [preMax, setPreMax] = useState(''); + const [preMin, setPreMin] = useState(''); - const maxDuration = duration.maxDuration || '0'; - const minDuration = duration.minDuration || '0'; + useEffect(() => { + const duration = getDuration || {}; - const [localMax, setLocalMax] = useState(maxDuration); - const [localMin, setLocalMin] = useState(minDuration); + const maxDuration = duration.maxDuration || '0'; + const minDuration = duration.minDuration || '0'; - const defaultValue = [parseFloat(minDuration), parseFloat(maxDuration)]; + if (preLocalMaxDuration.current === undefined) { + preLocalMaxDuration.current = parseFloat(maxDuration); + } + if (preLocalMinDuration.current === undefined) { + preLocalMinDuration.current = parseFloat(minDuration); + } + + setPreMax(maxDuration); + setPreMin(minDuration); + }, [getDuration]); + + const defaultValue = [parseFloat(preMin), parseFloat(preMax)]; const updatedUrl = async (min: number, max: number): Promise => { const preSelectedFilter = new Map(selectedFilter); @@ -74,7 +89,6 @@ function Duration(): JSX.Element { preSelectedFilter.set('duration', [String(max), String(min)]); - console.log('on the update Url'); const response = await getFilters({ end: String(globalTime.maxTime), getFilters: filterToFetchData, @@ -87,8 +101,9 @@ function Duration(): JSX.Element { const preFilter = getFilter(response.payload); preFilter.forEach((value, key) => { - if (key !== 'duration') { - preUserSelected.set(key, Object.keys(value)); + const values = Object.keys(value); + if (key !== 'duration' && values.length) { + preUserSelected.set(key, values); } }); @@ -102,6 +117,7 @@ function Duration(): JSX.Element { selectedTags, userSelected: preUserSelected, isFilterExclude, + order: spansAggregate.order, }, }); @@ -113,6 +129,7 @@ function Duration(): JSX.Element { preFilter, isFilterExclude, userSelectedFilter, + spansAggregate.order, ); } }; @@ -120,13 +137,12 @@ function Duration(): JSX.Element { const onRangeSliderHandler = (number: [number, number]): void => { const [min, max] = number; - setLocalMin(min.toString()); - setLocalMax(max.toString()); + setPreMin(min.toString()); + setPreMax(max.toString()); }; const debouncedFunction = useDebouncedFn( (min, max) => { - console.log('debounce function'); updatedUrl(min as number, max as number); }, 500, @@ -137,11 +153,9 @@ function Duration(): JSX.Element { event, ) => { const { value } = event.target; - const min = parseFloat(localMin); + const min = parseFloat(preMin); const max = parseFloat(value) * 1000000; - console.log('on change in max'); - onRangeSliderHandler([min, max]); debouncedFunction(min, max); }; @@ -151,9 +165,8 @@ function Duration(): JSX.Element { ) => { const { value } = event.target; const min = parseFloat(value) * 1000000; - const max = parseFloat(localMax); + const max = parseFloat(preMax); onRangeSliderHandler([min, max]); - console.log('on change in min'); debouncedFunction(min, max); }; @@ -170,7 +183,7 @@ function Duration(): JSX.Element { @@ -179,27 +192,27 @@ function Duration(): JSX.Element { { if (value === undefined) { return
; } - return
{`${getMs(value.toString())}ms`}
; + return
{`${getMs(value?.toString())}ms`}
; }} onChange={([min, max]): void => { onRangeSliderHandler([min, max]); }} onAfterChange={onRangeHandler} - value={[parseFloat(localMin), parseFloat(localMax)]} + value={[parseFloat(preMin), parseFloat(preMax)]} />
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx index 50686d1b78..8ab6e29d88 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx @@ -21,7 +21,7 @@ import { ButtonContainer, Container, IconContainer, - TextCotainer, + TextContainer, } from './styles'; const { Text } = Typography; @@ -64,16 +64,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { const getprepdatedSelectedFilter = new Map(selectedFilter); const getPreUserSelected = new Map(userSelectedFilter); - if (!isDefaultOpen) { - updatedFilterData = [PanelName]; - } else { - // removing the selected filter - updatedFilterData = [ - ...filterToFetchData.filter((name) => name !== PanelName), - ]; - getprepdatedSelectedFilter.delete(PanelName); - getPreUserSelected.delete(PanelName); - } + updatedFilterData = [PanelName]; const response = await getFilters({ end: String(global.maxTime), @@ -86,33 +77,14 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { if (response.statusCode === 200) { const updatedFilter = getFilter(response.payload); - // is closed - if (!isDefaultOpen) { - // getprepdatedSelectedFilter.set( - // props.name, - // Object.keys(updatedFilter.get(props.name) || {}), - // ); - + if (!getPreUserSelected.has(PanelName)) { getPreUserSelected.set( PanelName, - Object.keys(updatedFilter.get(PanelName) || {}), + Object.keys(updatedFilter.get(PanelName) || []), ); - - updatedFilterData = [...filterToFetchData, PanelName]; } - // now append the non prop.name trace filter enum over the list - // selectedFilter.forEach((value, key) => { - // if (key !== props.name) { - // getprepdatedSelectedFilter.set(key, value); - // } - // }); - - getPreUserSelected.forEach((value, key) => { - if (key !== PanelName) { - getPreUserSelected.set(key, value); - } - }); + updatedFilterData = [...filterToFetchData, PanelName]; filter.forEach((value, key) => { if (key !== PanelName) { updatedFilter.set(key, value); @@ -129,6 +101,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { selectedTags, userSelected: getPreUserSelected, isFilterExclude, + order: spansAggregate.order, }, }); @@ -140,6 +113,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { updatedFilter, isFilterExclude, getPreUserSelected, + spansAggregate.order, ); } else { notification.error({ @@ -155,6 +129,44 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { } }; + /** + * @description this function removes the selected filter + */ + const onCloseHandler = (): void => { + const preSelectedFilter = new Map(selectedFilter); + // removing the filter from filter to fetch the data + const preFilterToFetchTheData = [ + ...filterToFetchData.filter((name) => name !== PanelName), + ]; + + preSelectedFilter.delete(PanelName); + + dispatch({ + type: UPDATE_ALL_FILTERS, + payload: { + current: spansAggregate.currentPage, + filter, + filterToFetchData: preFilterToFetchTheData, + selectedFilter: preSelectedFilter, + selectedTags, + userSelected: userSelectedFilter, + isFilterExclude, + order: spansAggregate.order, + }, + }); + + updateURL( + preSelectedFilter, + preFilterToFetchTheData, + spansAggregate.currentPage, + selectedTags, + filter, + isFilterExclude, + userSelectedFilter, + spansAggregate.order, + ); + }; + const onClearAllHandler = async (): Promise => { try { setIsLoading(true); @@ -177,18 +189,19 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { }); if (response.statusCode === 200 && response.payload) { - const getUpatedFilter = getFilter(response.payload); + const getUpdatedFilter = getFilter(response.payload); dispatch({ type: UPDATE_ALL_FILTERS, payload: { current: spansAggregate.currentPage, - filter: getUpatedFilter, + filter: getUpdatedFilter, filterToFetchData, selectedFilter: updatedFilter, selectedTags, userSelected: preUserSelected, isFilterExclude: postIsFilterExclude, + order: spansAggregate.order, }, }); @@ -197,9 +210,10 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { filterToFetchData, spansAggregate.currentPage, selectedTags, - getUpatedFilter, + getUpdatedFilter, postIsFilterExclude, preUserSelected, + spansAggregate.order, ); } else { notification.error({ @@ -280,7 +294,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { aria-disabled={filterLoading || isLoading} aria-expanded={IsPanelOpen} > - + {!IsPanelOpen ? : } @@ -288,7 +302,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { {AllPanelHeading.find((e) => e.key === PanelName)?.displayValue || ''} - + {PanelName !== 'duration' && ( diff --git a/frontend/src/container/Trace/Filters/Panel/PanelHeading/styles.ts b/frontend/src/container/Trace/Filters/Panel/PanelHeading/styles.ts index d2c66cfb23..812be65ac7 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelHeading/styles.ts +++ b/frontend/src/container/Trace/Filters/Panel/PanelHeading/styles.ts @@ -30,7 +30,7 @@ export const IconContainer = styled.div` } `; -export const TextCotainer = styled.div` +export const TextContainer = styled.div` &&& { display: flex; cursor: pointer; diff --git a/frontend/src/container/Trace/Search/AllTags/index.tsx b/frontend/src/container/Trace/Search/AllTags/index.tsx index 91e2d08cb0..693f3c8451 100644 --- a/frontend/src/container/Trace/Search/AllTags/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/index.tsx @@ -5,7 +5,7 @@ import { connect, useSelector } from 'react-redux'; import { bindActionCreators } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError'; -import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity'; +import { UpdateTagVisibility } from 'store/actions/trace/updateTagPanelVisiblity'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import { TraceReducer } from 'types/reducer/trace'; @@ -27,7 +27,7 @@ const { Paragraph } = Typography; function AllTags({ updateTagIsError, onChangeHandler, - updateTagVisiblity, + updateTagVisibility, updateFilters, }: AllTagsProps): JSX.Element { const traces = useSelector((state) => state.traces); @@ -63,7 +63,7 @@ function AllTags({ onChangeHandler(parsedQuery.payload); updateFilters(localSelectedTags); updateTagIsError(false); - updateTagVisiblity(false); + updateTagVisibility(false); } }; @@ -75,7 +75,7 @@ function AllTags({ return ( - Unrecognised query format. Please reset your query by clicking `X` in the + Unrecognized query format. Please reset your query by clicking `X` in the search bar above. @@ -131,14 +131,14 @@ function AllTags({ interface DispatchProps { updateTagIsError: (value: boolean) => void; - updateTagVisiblity: (value: boolean) => void; + updateTagVisibility: (value: boolean) => void; } const mapDispatchToProps = ( dispatch: ThunkDispatch, ): DispatchProps => ({ updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch), - updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch), + updateTagVisibility: bindActionCreators(UpdateTagVisibility, dispatch), }); interface AllTagsProps extends DispatchProps { diff --git a/frontend/src/container/Trace/Search/index.tsx b/frontend/src/container/Trace/Search/index.tsx index cb8fdd453d..1590fd74b2 100644 --- a/frontend/src/container/Trace/Search/index.tsx +++ b/frontend/src/container/Trace/Search/index.tsx @@ -6,7 +6,7 @@ import { connect, useDispatch, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError'; -import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity'; +import { UpdateTagVisibility } from 'store/actions/trace/updateTagPanelVisiblity'; import { updateURL } from 'store/actions/trace/util'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; @@ -18,7 +18,7 @@ import { Container, SearchComponent } from './styles'; import { parseQueryToTags, parseTagsToQuery } from './util'; function Search({ - updateTagVisiblity, + updateTagVisibility, updateTagIsError, }: SearchProps): JSX.Element { const traces = useSelector((state) => state.traces); @@ -66,7 +66,7 @@ function Search({ !(e.ariaSelected === 'true') && traces.isTagModalOpen ) { - updateTagVisiblity(false); + updateTagVisibility(false); } }); @@ -75,7 +75,7 @@ function Search({ }; const setIsTagsModalHandler = (value: boolean): void => { - updateTagVisiblity(value); + updateTagVisibility(value); }; const onFocusHandler: React.FocusEventHandler = (e) => { @@ -96,6 +96,7 @@ function Search({ selectedFilter: traces.selectedFilter, userSelected: traces.userSelectedFilter, isFilterExclude: traces.isFilterExclude, + order: traces.spansAggregate.order, }, }); @@ -107,6 +108,7 @@ function Search({ traces.filter, traces.isFilterExclude, traces.userSelectedFilter, + traces.spansAggregate.order, ); }; @@ -124,7 +126,7 @@ function Search({ enterButton={} onSearch={(string): void => { if (string.length === 0) { - updateTagVisiblity(false); + updateTagVisibility(false); updateFilters([]); return; } @@ -135,7 +137,7 @@ function Search({ updateTagIsError(true); } else { updateTagIsError(false); - updateTagVisiblity(false); + updateTagVisibility(false); updateFilters(payload); } }} @@ -150,14 +152,14 @@ function Search({ } interface DispatchProps { - updateTagVisiblity: (value: boolean) => void; + updateTagVisibility: (value: boolean) => void; updateTagIsError: (value: boolean) => void; } const mapDispatchToProps = ( dispatch: ThunkDispatch, ): DispatchProps => ({ - updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch), + updateTagVisibility: bindActionCreators(UpdateTagVisibility, dispatch), updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch), }); diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index 944afddb45..b78d709b4f 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -4,34 +4,32 @@ import ROUTES from 'constants/routes'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import React from 'react'; -import { connect, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; -import { bindActionCreators } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { - GetSpansAggregate, - GetSpansAggregateProps, -} from 'store/actions/trace/getInitialSpansAggregate'; +import { Dispatch } from 'redux'; +import { updateURL } from 'store/actions/trace/util'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; -import { GlobalReducer } from 'types/reducer/globalTime'; +import { UPDATE_SPAN_ORDER } from 'types/actions/trace'; import { TraceReducer } from 'types/reducer/trace'; dayjs.extend(duration); -function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element { +function TraceTable(): JSX.Element { const { spansAggregate, selectedFilter, selectedTags, filterLoading, + userSelectedFilter, + filter, + isFilterExclude, + filterToFetchData, } = useSelector((state) => state.traces); - const globalTime = useSelector( - (state) => state.globalTime, - ); + const dispatch = useDispatch>(); - const { loading, total } = spansAggregate; + const { loading, total, order: spansAggregateOrder } = spansAggregate; type TableType = FlatArray; @@ -101,7 +99,8 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element { {`${dayjs .duration({ milliseconds: value / 1000000 }) - .asMilliseconds()} ms`} + .asMilliseconds() + .toFixed(2)} ms`} ), @@ -126,17 +125,27 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element { sort, ) => { if (!Array.isArray(sort)) { - const { order = 'ascend' } = sort; + const { order = spansAggregateOrder } = sort; if (props.current && props.pageSize) { - getSpansAggregate({ - maxTime: globalTime.maxTime, - minTime: globalTime.minTime, - selectedFilter, - current: props.current, - pageSize: props.pageSize, - selectedTags, - order: order === 'ascend' ? 'ascending' : 'descending', + const spanOrder = order || spansAggregateOrder; + + dispatch({ + type: UPDATE_SPAN_ORDER, + payload: { + order: spanOrder, + }, }); + + updateURL( + selectedFilter, + filterToFetchData, + props.current, + selectedTags, + filter, + isFilterExclude, + userSelectedFilter, + spanOrder, + ); } } }; @@ -147,7 +156,7 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element { dataSource={spansAggregate.data} loading={loading || filterLoading} columns={columns} - rowKey="timestamp" + rowKey={(record): string => `${record.traceID}-${record.spanID}`} style={{ cursor: 'pointer', }} @@ -158,20 +167,9 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element { position: ['bottomLeft'], total, }} + sortDirections={['ascend', 'descend']} /> ); } -interface DispatchProps { - getSpansAggregate: (props: GetSpansAggregateProps) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - getSpansAggregate: bindActionCreators(GetSpansAggregate, dispatch), -}); - -type TraceProps = DispatchProps; - -export default connect(null, mapDispatchToProps)(TraceTable); +export default TraceTable; diff --git a/frontend/src/pages/Trace/index.tsx b/frontend/src/pages/Trace/index.tsx index acdc32aeb1..427dd0d14e 100644 --- a/frontend/src/pages/Trace/index.tsx +++ b/frontend/src/pages/Trace/index.tsx @@ -63,7 +63,7 @@ function Trace({ current: spansAggregate.currentPage, pageSize: spansAggregate.pageSize, selectedTags, - order: 'ascending', + order: spansAggregate.order === 'ascend' ? 'ascending' : 'descending', }); }, [ selectedTags, @@ -73,6 +73,7 @@ function Trace({ getSpansAggregate, spansAggregate.currentPage, spansAggregate.pageSize, + spansAggregate.order, ]); useEffect(() => { @@ -93,8 +94,8 @@ function Trace({ selectedTags, maxTime, minTime, - isFilterExclude, getSpans, + isFilterExclude, ]); useEffect(() => { diff --git a/frontend/src/store/actions/trace/getInitialFilter.ts b/frontend/src/store/actions/trace/getInitialFilter.ts index ab0a49f79e..9b189cf60c 100644 --- a/frontend/src/store/actions/trace/getInitialFilter.ts +++ b/frontend/src/store/actions/trace/getInitialFilter.ts @@ -18,6 +18,7 @@ import { parseIsSkippedSelection, parseQueryIntoCurrent, parseQueryIntoFilter, + parseQueryIntoOrder, parseQueryIntoSelectedTags, parseSelectedFilter, } from './util'; @@ -66,6 +67,11 @@ export const GetInitialTraceFilter = ( traces.spansAggregate.currentPage, ); + const parsedQueryOrder = parseQueryIntoOrder( + query, + traces.spansAggregate.order, + ); + const isSelectionSkipped = parseIsSkippedSelection(query); const parsedSelectedTags = parseQueryIntoSelectedTags( @@ -148,6 +154,7 @@ export const GetInitialTraceFilter = ( selectedTags: parsedSelectedTags.currentValue, userSelected: getUserSelected.currentValue, isFilterExclude: getIsFilterExcluded.currentValue, + order: parsedQueryOrder.currentValue, }, }); } else { diff --git a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts index 11d061e079..a0725c9535 100644 --- a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts +++ b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts @@ -3,11 +3,13 @@ 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 { UPDATE_SPANS_AGGREGATE } from 'types/actions/trace'; import { Props as GetSpanAggregateProps } from 'types/api/trace/getSpanAggregate'; import { GlobalReducer } from 'types/reducer/globalTime'; import { TraceReducer } from 'types/reducer/trace'; +import { updateURL } from './util'; + export const GetSpansAggregate = ( props: GetSpansAggregateProps, ): (( @@ -29,10 +31,12 @@ export const GetSpansAggregate = ( return; } + const order = props.order === 'ascending' ? 'ascend' : 'descend'; + try { // triggering loading dispatch({ - type: UPDATE_SPANS_AGGREEGATE, + type: UPDATE_SPANS_AGGREGATE, payload: { spansAggregate: { currentPage: props.current, @@ -41,6 +45,7 @@ export const GetSpansAggregate = ( error: false, total: spansAggregate.total, pageSize: props.pageSize, + order, }, }, }); @@ -53,12 +58,12 @@ export const GetSpansAggregate = ( offset: props.current * props.pageSize - props.pageSize, selectedTags: props.selectedTags, isFilterExclude: traces.isFilterExclude, - order: props.order, + order, }); if (response.statusCode === 200) { dispatch({ - type: UPDATE_SPANS_AGGREEGATE, + type: UPDATE_SPANS_AGGREGATE, payload: { spansAggregate: { currentPage: props.current, @@ -67,16 +72,28 @@ export const GetSpansAggregate = ( error: false, total: response.payload.totalSpans, pageSize: props.pageSize, + order, }, }, }); + + updateURL( + traces.selectedFilter, + traces.filterToFetchData, + props.current, + traces.selectedTags, + traces.filter, + traces.isFilterExclude, + traces.userSelectedFilter, + order, + ); } else { notification.error({ message: response.error || 'Something went wrong', }); dispatch({ - type: UPDATE_SPANS_AGGREEGATE, + type: UPDATE_SPANS_AGGREGATE, payload: { spansAggregate: { currentPage: props.current, @@ -85,13 +102,14 @@ export const GetSpansAggregate = ( error: true, total: spansAggregate.total, pageSize: props.pageSize, + order, }, }, }); } } catch (error) { dispatch({ - type: UPDATE_SPANS_AGGREEGATE, + type: UPDATE_SPANS_AGGREGATE, payload: { spansAggregate: { currentPage: props.current, @@ -100,6 +118,7 @@ export const GetSpansAggregate = ( error: true, total: spansAggregate.total, pageSize: props.pageSize, + order, }, }, }); diff --git a/frontend/src/store/actions/trace/parseFilter/index.ts b/frontend/src/store/actions/trace/parseFilter/index.ts index fc7a14bc9f..f2489c2076 100644 --- a/frontend/src/store/actions/trace/parseFilter/index.ts +++ b/frontend/src/store/actions/trace/parseFilter/index.ts @@ -1,4 +1,3 @@ -export * from './current'; export * from './filter'; export * from './filterToFetchData'; export * from './isFilterExclude'; @@ -6,3 +5,5 @@ export * from './minMaxTime'; export * from './selectedFilter'; export * from './selectedTags'; export * from './skippedSelected'; +export * from './spanAggregateCurrentPage'; +export * from './spanAggregateOrder'; diff --git a/frontend/src/store/actions/trace/parseFilter/current.ts b/frontend/src/store/actions/trace/parseFilter/spanAggregateCurrentPage.ts similarity index 93% rename from frontend/src/store/actions/trace/parseFilter/current.ts rename to frontend/src/store/actions/trace/parseFilter/spanAggregateCurrentPage.ts index 4bdd0aa7f7..3135992558 100644 --- a/frontend/src/store/actions/trace/parseFilter/current.ts +++ b/frontend/src/store/actions/trace/parseFilter/spanAggregateCurrentPage.ts @@ -10,7 +10,7 @@ export const parseQueryIntoCurrent = ( let current = 1; - const selected = url.get('current'); + const selected = url.get('spanAggregateCurrentPage'); if (selected) { try { diff --git a/frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts b/frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts new file mode 100644 index 0000000000..1641ebe8f2 --- /dev/null +++ b/frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts @@ -0,0 +1,39 @@ +import { TraceReducer } from 'types/reducer/trace'; + +import { ParsedUrl } from '../util'; + +export const parseQueryIntoOrder = ( + query: string, + stateCurrent: TraceReducer['spansAggregate']['order'], +): ParsedUrl => { + const url = new URLSearchParams(query); + + let current = 'ascend'; + + const selected = url.get('spanAggregateOrder'); + + if (selected) { + try { + const parsedValue = selected; + + if (parsedValue && typeof parsedValue === 'string') { + current = parsedValue; + } + } catch (error) { + console.log(error); + console.log('error while parsing json'); + } + } + + if (selected) { + return { + currentValue: current, + urlValue: current, + }; + } + + return { + currentValue: stateCurrent, + urlValue: current, + }; +}; diff --git a/frontend/src/store/actions/trace/selectTraceFilter.ts b/frontend/src/store/actions/trace/selectTraceFilter.ts index f23a361f0a..14e6b39fb4 100644 --- a/frontend/src/store/actions/trace/selectTraceFilter.ts +++ b/frontend/src/store/actions/trace/selectTraceFilter.ts @@ -47,6 +47,7 @@ export const SelectedTraceFilter = (props: { traces.filter, traces.isFilterExclude, traces.userSelectedFilter, + traces.spansAggregate.order, ); }; }; diff --git a/frontend/src/store/actions/trace/updateTagPanelVisiblity.ts b/frontend/src/store/actions/trace/updateTagPanelVisiblity.ts index 9fce20d0de..59fb070007 100644 --- a/frontend/src/store/actions/trace/updateTagPanelVisiblity.ts +++ b/frontend/src/store/actions/trace/updateTagPanelVisiblity.ts @@ -1,14 +1,14 @@ import { Dispatch } from 'redux'; import AppActions from 'types/actions'; -import { UPDATE_TAG_MODAL_VISIBLITY } from 'types/actions/trace'; +import { UPDATE_TAG_MODAL_VISIBILITY } from 'types/actions/trace'; import { TraceReducer } from 'types/reducer/trace'; -export const UpdateTagVisiblity = ( +export const UpdateTagVisibility = ( isTagModalOpen: TraceReducer['isTagModalOpen'], ): ((dispatch: Dispatch) => void) => { return (dispatch): void => { dispatch({ - type: UPDATE_TAG_MODAL_VISIBLITY, + type: UPDATE_TAG_MODAL_VISIBILITY, payload: { isTagModalOpen, }, diff --git a/frontend/src/store/actions/trace/util.ts b/frontend/src/store/actions/trace/util.ts index 63d91216f7..5355fc0660 100644 --- a/frontend/src/store/actions/trace/util.ts +++ b/frontend/src/store/actions/trace/util.ts @@ -18,11 +18,12 @@ export function isTraceFilterEnum( export const updateURL = ( selectedFilter: TraceReducer['selectedFilter'], filterToFetchData: TraceReducer['filterToFetchData'], - current: TraceReducer['spansAggregate']['total'], + spanAggregateCurrentPage: TraceReducer['spansAggregate']['currentPage'], selectedTags: TraceReducer['selectedTags'], filter: TraceReducer['filter'], isFilterExclude: TraceReducer['isFilterExclude'], userSelectedFilter: TraceReducer['userSelectedFilter'], + spanAggregateOrder: TraceReducer['spansAggregate']['order'], ): void => { const search = new URLSearchParams(window.location.search); const preResult: { key: string; value: string }[] = []; @@ -30,11 +31,12 @@ export const updateURL = ( const keyToSkip = [ 'selected', 'filterToFetchData', - 'current', 'selectedTags', 'filter', 'isFilterExclude', 'userSelectedFilter', + 'spanAggregateCurrentPage', + 'spanAggregateOrder', ]; search.forEach((value, key) => { @@ -51,7 +53,7 @@ export const updateURL = ( Object.fromEntries(selectedFilter), )}&filterToFetchData=${JSON.stringify( filterToFetchData, - )}¤t=${current}&selectedTags=${JSON.stringify( + )}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&selectedTags=${JSON.stringify( selectedTags, )}&filter=${JSON.stringify(Object.fromEntries(filter))}&${preResult .map((e) => `${e.key}=${e.value}`) @@ -59,7 +61,7 @@ export const updateURL = ( Object.fromEntries(isFilterExclude), )}&userSelectedFilter=${JSON.stringify( Object.fromEntries(userSelectedFilter), - )}`, + )}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&spanAggregateOrder=${spanAggregateOrder}`, ); }; diff --git a/frontend/src/store/reducers/trace.ts b/frontend/src/store/reducers/trace.ts index 81b27b7e9e..46328c4858 100644 --- a/frontend/src/store/reducers/trace.ts +++ b/frontend/src/store/reducers/trace.ts @@ -9,8 +9,9 @@ import { UPDATE_SELECTED_FUNCTION, UPDATE_SELECTED_GROUP_BY, UPDATE_SELECTED_TAGS, - UPDATE_SPANS_AGGREEGATE, - UPDATE_TAG_MODAL_VISIBLITY, + UPDATE_SPAN_ORDER, + UPDATE_SPANS_AGGREGATE, + UPDATE_TAG_MODAL_VISIBILITY, UPDATE_TRACE_FILTER, UPDATE_TRACE_FILTER_LOADING, UPDATE_TRACE_GRAPH_ERROR, @@ -37,6 +38,7 @@ const initialValue: TraceReducer = { error: false, total: 0, pageSize: 10, + order: 'ascend', }, selectedGroupBy: '', selectedFunction: 'count', @@ -71,6 +73,7 @@ const traceReducer = ( selectedTags, userSelected, isFilterExclude, + order, } = payload; return { @@ -84,6 +87,7 @@ const traceReducer = ( spansAggregate: { ...state.spansAggregate, currentPage: current, + order, }, }; } @@ -115,14 +119,14 @@ const traceReducer = ( }; } - case UPDATE_SPANS_AGGREEGATE: { + case UPDATE_SPANS_AGGREGATE: { return { ...state, spansAggregate: action.payload.spansAggregate, }; } - case UPDATE_TAG_MODAL_VISIBLITY: { + case UPDATE_TAG_MODAL_VISIBILITY: { return { ...state, isTagModalOpen: action.payload.isTagModalOpen, @@ -199,6 +203,16 @@ const traceReducer = ( }; } + case UPDATE_SPAN_ORDER: { + return { + ...state, + spansAggregate: { + ...state.spansAggregate, + order: action.payload.order, + }, + }; + } + default: return state; } diff --git a/frontend/src/types/actions/trace.ts b/frontend/src/types/actions/trace.ts index 9c22a55220..c6f1f12bba 100644 --- a/frontend/src/types/actions/trace.ts +++ b/frontend/src/types/actions/trace.ts @@ -7,9 +7,9 @@ export const UPDATE_TRACE_FILTER_LOADING = 'UPDATE_TRACE_FILTER_LOADING'; export const SELECT_TRACE_FILTER = 'SELECT_TRACE_FILTER'; export const UPDATE_ALL_FILTERS = 'UPDATE_ALL_FILTERS'; export const UPDATE_SELECTED_TAGS = 'UPDATE_SELECTED_TAGS'; -export const UPDATE_TAG_MODAL_VISIBLITY = 'UPDATE_TAG_MODAL_VISIBLITY'; +export const UPDATE_TAG_MODAL_VISIBILITY = 'UPDATE_TAG_MODAL_VISIBILITY'; -export const UPDATE_SPANS_AGGREEGATE = 'UPDATE_SPANS_AGGREEGATE'; +export const UPDATE_SPANS_AGGREGATE = 'UPDATE_SPANS_AGGREGATE'; export const UPDATE_IS_TAG_ERROR = 'UPDATE_IS_TAG_ERROR'; @@ -25,6 +25,8 @@ export const UPDATE_FILTER_RESPONSE_SELECTED = 'UPDATE_FILTER_RESPONSE_SELECTED'; export const UPDATE_FILTER_EXCLUDE = 'UPDATE_FILTER_EXCLUDE'; +export const UPDATE_SPAN_ORDER = 'UPDATE_SPAN_ORDER'; + export interface UpdateFilter { type: typeof UPDATE_TRACE_FILTER; payload: { @@ -33,14 +35,14 @@ export interface UpdateFilter { } export interface UpdateSpansAggregate { - type: typeof UPDATE_SPANS_AGGREEGATE; + type: typeof UPDATE_SPANS_AGGREGATE; payload: { spansAggregate: TraceReducer['spansAggregate']; }; } -export interface UpdateTagVisiblity { - type: typeof UPDATE_TAG_MODAL_VISIBLITY; +export interface UpdateTagVisibility { + type: typeof UPDATE_TAG_MODAL_VISIBILITY; payload: { isTagModalOpen: TraceReducer['isTagModalOpen']; }; @@ -70,6 +72,7 @@ export interface UpdateAllFilters { selectedTags: TraceReducer['selectedTags']; userSelected: TraceReducer['userSelectedFilter']; isFilterExclude: TraceReducer['isFilterExclude']; + order: TraceReducer['spansAggregate']['order']; }; } @@ -149,6 +152,13 @@ export interface UpdateSpans { }; } +export interface UpdateSpanOrder { + type: typeof UPDATE_SPAN_ORDER; + payload: { + order: TraceReducer['spansAggregate']['order']; + }; +} + export type TraceActions = | UpdateFilter | GetTraceFilter @@ -156,7 +166,7 @@ export type TraceActions = | SelectTraceFilter | UpdateAllFilters | UpdateSelectedTags - | UpdateTagVisiblity + | UpdateTagVisibility | UpdateSpansAggregate | UpdateIsTagError | UpdateSelectedGroupBy @@ -166,4 +176,5 @@ export type TraceActions = | UpdateSpans | ResetTraceFilter | UpdateSelected - | UpdateFilterExclude; + | UpdateFilterExclude + | UpdateSpanOrder; diff --git a/frontend/src/types/api/trace/getSpanAggregate.ts b/frontend/src/types/api/trace/getSpanAggregate.ts index 3263621009..5f6d413a81 100644 --- a/frontend/src/types/api/trace/getSpanAggregate.ts +++ b/frontend/src/types/api/trace/getSpanAggregate.ts @@ -7,7 +7,7 @@ export interface Props { limit: number; offset: number; selectedTags: TraceReducer['selectedTags']; - order?: 'descending' | 'ascending'; + order?: TraceReducer['spansAggregate']['order']; isFilterExclude: TraceReducer['isFilterExclude']; } diff --git a/frontend/src/types/reducer/trace.ts b/frontend/src/types/reducer/trace.ts index a033bf2879..339fd63483 100644 --- a/frontend/src/types/reducer/trace.ts +++ b/frontend/src/types/reducer/trace.ts @@ -4,8 +4,10 @@ export interface TraceReducer { filter: Map>; filterToFetchData: TraceFilterEnum[]; filterLoading: boolean; + selectedFilter: Map; userSelectedFilter: Map; + isFilterExclude: Map; selectedTags: Tags[]; isTagModalOpen: boolean; @@ -18,6 +20,7 @@ export interface TraceReducer { error: boolean; total: number; pageSize: number; + order: string; }; selectedGroupBy: string; selectedFunction: string; From a8c5934fc5c8a15efc04161f5f1a50c8416fd41c Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Tue, 5 Apr 2022 14:47:37 +0530 Subject: [PATCH 027/154] fix: Fix jest (#945) * bug: jest is now fixed * chore: files are included for the eslint * chore: build is fixed * test: jest test are fixed --- frontend/babel.config.js | 6 + frontend/jest.config.ts | 17 +- frontend/package.json | 4 +- .../src/components/NotFound/NotFound.test.tsx | 4 + .../__snapshots__/NotFound.test.tsx.snap | 10 +- .../__tests__/TraceFlameGraph.test.tsx | 71 +- .../TraceFlameGraph.test.tsx.snap | 16 +- frontend/tsconfig.json | 2 +- frontend/yarn.lock | 1111 ++++++++--------- 9 files changed, 621 insertions(+), 620 deletions(-) create mode 100644 frontend/babel.config.js diff --git a/frontend/babel.config.js b/frontend/babel.config.js new file mode 100644 index 0000000000..b3df57dfb0 --- /dev/null +++ b/frontend/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ], +}; diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index a949a7611b..5b5ebcdbc7 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -9,12 +9,19 @@ const config: Config.InitialOptions = { moduleNameMapper: { '\\.(css|less)$': '/__mocks__/cssMock.ts', }, - notify: true, - notifyMode: 'always', - testMatch: ['/src/**/?(*.)(test).(ts|js)?(x)'], - transform: { - '\\.(js|jsx|ts|tsx)?$': 'babel-jest', + globals: { + extensionsToTreatAsEsm: ['.ts'], + 'ts-jest': { + useESM: true, + }, }, + testMatch: ['/src/**/?(*.)(test).(ts|js)?(x)'], + preset: 'ts-jest/presets/js-with-ts-esm', + transform: { + '^.+\\.(ts|tsx)?$': 'ts-jest', + '^.+\\.(js|jsx)$': 'babel-jest', + }, + transformIgnorePatterns: ['node_modules/(?!(lodash-es)/)'], setupFilesAfterEnv: ['jest.setup.ts'], testPathIgnorePatterns: ['/node_modules/', '/public/'], moduleDirectories: ['node_modules', 'src'], diff --git a/frontend/package.json b/frontend/package.json index 28666512fb..88f09caa99 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,7 +57,7 @@ "i18next": "^21.6.12", "i18next-browser-languagedetector": "^6.1.3", "i18next-http-backend": "^1.3.2", - "jest": "26.6.0", + "jest": "^27.5.1", "less": "^4.1.2", "less-loader": "^10.2.0", "lodash-es": "^4.17.21", @@ -107,6 +107,7 @@ "@babel/preset-typescript": "^7.12.17", "@jest/globals": "^27.5.1", "@testing-library/cypress": "^8.0.0", + "@testing-library/react-hooks": "^7.0.2", "@types/color": "^3.0.3", "@types/compression-webpack-plugin": "^9.0.0", "@types/copy-webpack-plugin": "^8.0.1", @@ -155,6 +156,7 @@ "portfinder-sync": "^0.0.2", "prettier": "2.2.1", "react-hot-loader": "^4.13.0", + "ts-jest": "^27.1.4", "ts-node": "^10.2.1", "typescript-plugin-css-modules": "^3.4.0", "webpack-bundle-analyzer": "^4.5.0", diff --git a/frontend/src/components/NotFound/NotFound.test.tsx b/frontend/src/components/NotFound/NotFound.test.tsx index c8aab78ef7..3f4be8c009 100644 --- a/frontend/src/components/NotFound/NotFound.test.tsx +++ b/frontend/src/components/NotFound/NotFound.test.tsx @@ -1,3 +1,7 @@ +/** + * @jest-environment jsdom + */ + import { expect } from '@jest/globals'; import { render } from '@testing-library/react'; import React from 'react'; diff --git a/frontend/src/components/NotFound/__snapshots__/NotFound.test.tsx.snap b/frontend/src/components/NotFound/__snapshots__/NotFound.test.tsx.snap index e65af86a8a..0e9ce92e30 100644 --- a/frontend/src/components/NotFound/__snapshots__/NotFound.test.tsx.snap +++ b/frontend/src/components/NotFound/__snapshots__/NotFound.test.tsx.snap @@ -3,7 +3,7 @@ exports[`Not Found page test should render Not Found page without errors 1`] = `

Ah, seems like we reached a dead end!

Page Not Found

diff --git a/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx b/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx index f97e41764c..e17784cbbd 100644 --- a/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx +++ b/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx @@ -1,42 +1,51 @@ +/** + * @jest-environment jsdom + */ + import { expect } from '@jest/globals'; import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import TraceFlameGraph from 'container/TraceFlameGraph'; import React, { useState } from 'react'; +import { Provider } from 'react-redux'; +import store from 'store'; test('loads and displays greeting', () => { - const onSpanHover = useState(''); + const { rerender } = renderHook(() => useState('')); const { asFragment } = render( - {}, - selectedSpanId: '', - traceMetaData: { - globalEnd: 0, - globalStart: 0, - levels: 0, - spread: 0, - totalSpans: 0, - }, - treeData: { - children: [], - id: '', - name: '', - serviceColour: '', - serviceName: '', - startTime: 0, - tags: [], - time: 0, - value: 0, - event: [], - hasError: false, - parent: undefined, - }, - }} - />, + + {}, + selectedSpanId: '', + traceMetaData: { + globalEnd: 0, + globalStart: 0, + levels: 0, + spread: 0, + totalSpans: 0, + }, + treeData: { + children: [], + id: '', + name: '', + serviceColour: '#a0e', + serviceName: '', + startTime: 0, + tags: [], + time: 100, + value: 100, + event: [], + hasError: false, + parent: undefined, + }, + }} + /> + , ); 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 index ec5ff7b75b..78b91ec72d 100644 --- a/frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap +++ b/frontend/src/container/TraceFlameGraph/__tests__/__snapshots__/TraceFlameGraph.test.tsx.snap @@ -1,3 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`loads and displays greeting 1`] = ``; +exports[`loads and displays greeting 1`] = ` + +
+
+
+ +`; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 3ff85194f8..fcc3663f1b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -22,5 +22,5 @@ "plugins": [{ "name": "typescript-plugin-css-modules" }] }, "exclude": ["node_modules"], - "include": ["./src"] + "include": ["./src", "./babel.config.js", "./jest.config.ts"] } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 13f9bd136c..5f063ef3f2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -92,7 +92,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.5": +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a" integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ== @@ -113,7 +113,7 @@ json5 "^2.1.2" semver "^6.3.0" -"@babel/generator@^7.17.3", "@babel/generator@^7.17.7": +"@babel/generator@^7.17.3", "@babel/generator@^7.17.7", "@babel/generator@^7.7.2": version "7.17.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== @@ -631,7 +631,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.16.7": +"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.7.2": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== @@ -1086,7 +1086,7 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0": +"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": version "7.17.3" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== @@ -1281,62 +1281,52 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" -"@jest/core@^26.6.0", "@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" + emittery "^0.8.1" exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== - dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - "@jest/environment@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" @@ -1347,18 +1337,6 @@ "@types/node" "*" jest-mock "^27.5.1" -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== - dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" - "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" - "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" @@ -1371,15 +1349,6 @@ jest-mock "^27.5.1" jest-util "^27.5.1" -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" - "@jest/globals@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" @@ -1389,67 +1358,65 @@ "@jest/types" "^27.5.1" expect "^27.5.1" -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" + istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" + v8-to-istanbul "^8.1.0" -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== dependencies: callsites "^3.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" source-map "^0.6.0" -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" "@jest/transform@^26.6.2": version "26.6.2" @@ -1472,6 +1439,27 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + "@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" @@ -1594,13 +1582,6 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== - dependencies: - "@sinonjs/commons" "^1.7.0" - "@sinonjs/fake-timers@^8.0.1": version "8.1.0" resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" @@ -1659,6 +1640,17 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@^7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" + integrity sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + react-error-boundary "^3.1.0" + "@testing-library/react@^11.1.0": version "11.2.7" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818" @@ -1714,7 +1706,7 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== @@ -2212,17 +2204,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.26.tgz#63d204d136c9916fb4dcd1b50f9740fe86884e47" integrity sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ== -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.0.0": +"@types/prettier@^2.1.5": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA== @@ -2242,6 +2229,13 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/react-dom@>=16.9.0": + version "17.0.14" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.14.tgz#c8f917156b652ddf807711f5becbd2ab018dea9f" + integrity sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ== + dependencies: + "@types/react" "*" + "@types/react-dom@^16.9.9": version "16.9.14" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.14.tgz#674b8f116645fe5266b40b525777fc6bb8eb3bcd" @@ -2283,6 +2277,13 @@ "@types/history" "^4.7.11" "@types/react" "*" +"@types/react-test-renderer@>=16.9.0": + version "17.0.1" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b" + integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^17.0.0": version "17.0.41" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.41.tgz#6e179590d276394de1e357b3f89d05d7d3da8b85" @@ -2292,6 +2293,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@>=16.9.0": + version "17.0.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" + integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/react@^16": version "16.14.24" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.24.tgz#f2c5e9fa78f83f769884b83defcf7924b9eb5c82" @@ -3237,7 +3247,7 @@ babel-helper-to-multiple-sequence-expressions@^0.5.0: resolved "https://registry.yarnpkg.com/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz#a3f924e3561882d42fcf48907aa98f7979a4588d" integrity sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA== -babel-jest@^26.6.0, babel-jest@^26.6.3: +babel-jest@^26.6.0: version "26.6.3" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== @@ -3251,6 +3261,20 @@ babel-jest@^26.6.0, babel-jest@^26.6.3: graceful-fs "^4.2.4" slash "^3.0.0" +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + babel-loader@8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" @@ -3269,7 +3293,7 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-istanbul@^6.0.0: +babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== @@ -3290,6 +3314,16 @@ babel-plugin-jest-hoist@^26.6.2: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" @@ -3520,6 +3554,14 @@ babel-preset-jest@^26.6.2: babel-plugin-jest-hoist "^26.6.2" babel-preset-current-node-syntax "^1.0.0" +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== + dependencies: + babel-plugin-jest-hoist "^27.5.1" + babel-preset-current-node-syntax "^1.0.0" + babel-preset-minify@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz#25f5d0bce36ec818be80338d0e594106e21eaa9f" @@ -3711,6 +3753,13 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4 node-releases "^2.0.2" picocolors "^1.0.0" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -3794,12 +3843,12 @@ camel-case@^4.1.1: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -3932,10 +3981,10 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== class-utils@^0.3.5: version "0.3.6" @@ -3996,14 +4045,14 @@ cli-truncate@^3.1.0: slice-ansi "^5.0.0" string-width "^5.0.0" -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" + wrap-ansi "^7.0.0" clone-deep@^4.0.1: version "4.0.1" @@ -5219,11 +5268,6 @@ debug@~3.1.0: 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" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - decimal.js@^10.2.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" @@ -5241,6 +5285,11 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + deep-assign@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-2.0.0.tgz#ebe06b1f07f08dae597620e3dd1622f371a1c572" @@ -5530,10 +5579,10 @@ emitter-component@^1.1.1: resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6" integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY= -emittery@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" - integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== emoji-regex@^8.0.0: version "8.0.0" @@ -6009,7 +6058,7 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== -execa@4.1.0, execa@^4.0.0: +execa@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -6077,18 +6126,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" - integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== - dependencies: - "@jest/types" "^26.6.2" - ansi-styles "^4.0.0" - jest-get-type "^26.3.0" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - expect@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" @@ -6211,7 +6248,7 @@ fast-glob@^3.2.5, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -6467,7 +6504,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.1.2, fsevents@~2.3.2: +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -6507,7 +6544,7 @@ geotiff@^1.0.8: threads "^1.7.0" xml-utils "^1.0.2" -get-caller-file@^2.0.1: +get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -6645,11 +6682,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -6756,11 +6788,6 @@ hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react- dependencies: react-is "^16.7.0" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -7449,17 +7476,7 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== - dependencies: - "@babel/core" "^7.7.5" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - -istanbul-lib-instrument@^5.0.4: +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== @@ -7488,7 +7505,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2: +istanbul-reports@^3.1.3: version "3.1.4" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== @@ -7501,59 +7518,89 @@ jerrypick@^1.0.4: resolved "https://registry.yarnpkg.com/jerrypick/-/jerrypick-1.0.5.tgz#808a2a53acd332b1d972a097c510c5d28fb8e3e2" integrity sha512-95dIyL2QXqevDDJ70rmiqKLsTi+riZftLQo708tSFR/O5pQOf6VscDhjnN8mkNQwqnQmGhw+6dfZ5d4bmH/yww== -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" -jest-cli@^26.6.0: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" exit "^0.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" prompts "^2.0.1" - yargs "^15.4.1" + yargs "^16.2.0" -jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" chalk "^4.0.0" + ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" -jest-diff@^26.0.0, jest-diff@^26.6.2: +jest-diff@^26.0.0: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== @@ -7573,48 +7620,48 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== dependencies: detect-newline "^3.0.0" -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-mock "^27.5.1" + jest-util "^27.5.1" jest-get-type@^26.3.0: version "26.3.0" @@ -7647,47 +7694,56 @@ jest-haste-map@^26.6.2: optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.6.2" + expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - -jest-matcher-utils@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" - integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== - dependencies: - chalk "^4.0.0" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: version "27.5.1" @@ -7699,21 +7755,6 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-message-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" - integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.6.2" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - pretty-format "^26.6.2" - slash "^3.0.0" - stack-utils "^2.0.2" - jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" @@ -7729,14 +7770,6 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" @@ -7755,87 +7788,90 @@ jest-regex-util@^26.0.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== - dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-resolve@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" - integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" + +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== + dependencies: + "@jest/types" "^27.5.1" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" jest-pnp-resolver "^1.2.2" - jest-util "^26.6.2" - read-pkg-up "^7.0.1" - resolve "^1.18.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" source-map-support "^0.5.6" - throat "^5.0.0" + throat "^6.0.1" -jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" - cjs-module-lexer "^0.6.0" + cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - exit "^0.1.2" + execa "^5.0.0" glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.4.1" jest-serializer@^26.6.2: version "26.6.2" @@ -7845,26 +7881,40 @@ jest-serializer@^26.6.2: "@types/node" "*" graceful-fs "^4.2.4" -jest-snapshot@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" - integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" "@babel/types" "^7.0.0" - "@jest/types" "^26.6.2" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.0.0" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^26.6.2" - graceful-fs "^4.2.4" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - jest-haste-map "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" natural-compare "^1.4.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" semver "^7.3.2" jest-util@^26.6.2: @@ -7879,7 +7929,7 @@ jest-util@^26.6.2: is-ci "^2.0.0" micromatch "^4.0.2" -jest-util@^27.5.1: +jest-util@^27.0.0, jest-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== @@ -7891,29 +7941,29 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" + "@jest/types" "^27.5.1" + camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^26.3.0" + jest-get-type "^27.5.1" leven "^3.1.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.6.2" + jest-util "^27.5.1" string-length "^4.0.1" jest-worker@^26.6.2: @@ -7925,7 +7975,7 @@ jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^27.0.2, jest-worker@^27.4.5: +jest-worker@^27.0.2, jest-worker@^27.4.5, jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== @@ -7934,14 +7984,14 @@ jest-worker@^27.0.2, jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@26.6.0: - version "26.6.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.0.tgz#546b25a1d8c888569dbbe93cae131748086a4a25" - integrity sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA== +jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== dependencies: - "@jest/core" "^26.6.0" + "@jest/core" "^27.5.1" import-local "^3.0.2" - jest-cli "^26.6.0" + jest-cli "^27.5.1" js-cookie@^2.2.1: version "2.2.1" @@ -7966,7 +8016,7 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.4.0: +jsdom@^16.6.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -8056,6 +8106,11 @@ json2mq@^0.2.0: dependencies: string-convert "^0.2.0" +json5@2.x, json5@^2.1.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + json5@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -8068,11 +8123,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -8373,7 +8423,7 @@ lodash.isequal@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.memoize@^4.1.2: +lodash.memoize@4.x, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= @@ -8472,7 +8522,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@^1.1.1: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -8866,33 +8916,11 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= -node-notifier@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" - integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - node-releases@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -9147,11 +9175,6 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= -p-each-series@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" - integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -9272,7 +9295,7 @@ parse-headers@^2.0.0, parse-headers@^2.0.2: resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== -parse-json@^5.0.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -9422,7 +9445,7 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pirates@^4.0.1: +pirates@^4.0.1, pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== @@ -10394,6 +10417,13 @@ react-draggable@^4.0.0, react-draggable@^4.0.3: clsx "^1.1.1" prop-types "^15.6.0" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-force-graph@^1.41.0: version "1.41.12" resolved "https://registry.yarnpkg.com/react-force-graph/-/react-force-graph-1.41.12.tgz#ceffca39f50f3cf0528b48df846ae667058dc98c" @@ -10597,25 +10627,6 @@ react@17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - readable-stream@^2.0.1: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -10789,11 +10800,6 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -10843,7 +10849,12 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -11056,28 +11067,28 @@ selfsigned@^2.0.0: dependencies: node-forge "^1.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: +semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" +semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" @@ -11134,11 +11145,6 @@ serve-static@1.14.2: parseurl "~1.3.3" send "0.17.2" -set-blocking@^2.0.0: - version "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" @@ -11200,11 +11206,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -11409,32 +11410,6 @@ sourcemap-codec@^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" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" - integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== - spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -11497,7 +11472,7 @@ stack-generator@^2.0.5: dependencies: stackframe "^1.1.1" -stack-utils@^2.0.2, stack-utils@^2.0.3: +stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== @@ -11941,10 +11916,10 @@ three-render-objects@^1.26: resolved "https://registry.yarnpkg.com/three/-/three-0.138.3.tgz#7be5153d79dcbf9e9baad82e7faf8c29edda4ed0" integrity sha512-4t1cKC8gimNyJChJbaklg8W/qj3PpsLJUIFm5LIuAy/hVxxNm1ru2FGTSfbTSsuHmC/7ipsyuGKqrSAKLNtkzg== -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== throttle-debounce@^3.0.1: version "3.0.1" @@ -12096,6 +12071,20 @@ ts-easing@^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-jest@^27.1.4: + version "27.1.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00" + integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" + ts-node@^10.2.1: version "10.7.0" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" @@ -12192,16 +12181,6 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -12367,7 +12346,7 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uuid@^8.3.0, uuid@^8.3.2: +uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -12382,23 +12361,15 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -v8-to-istanbul@^7.0.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" - integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" source-map "^0.7.3" -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" @@ -12700,11 +12671,6 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -12712,7 +12678,7 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -12846,10 +12812,10 @@ xtend@^4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^4.0.0: version "4.0.0" @@ -12861,30 +12827,23 @@ yaml@^1.10.0, yaml@^1.10.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@20.x, yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@^15.4.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" + y18n "^5.0.5" + yargs-parser "^20.2.2" yauzl@^2.10.0: version "2.10.0" From 95f8dfb4bc8a2fd079ff0e8f2cf187cb9bfdc1eb Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 15:44:01 +0530 Subject: [PATCH 028/154] feat: i18n support for settings page --- frontend/public/locales/en/translation.json | 13 ++++++- .../src/container/GeneralSettings/index.tsx | 34 +++++++++---------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index e63d6d083e..e18493e548 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -1,3 +1,14 @@ { - "monitor_signup": "Monitor your applications. Find what is causing issues." + "monitor_signup": "Monitor your applications. Find what is causing issues.", + "settings": { + "total_retention_period": "Total Retention Period", + "move_to_s3": "Move to S3\n(should be lower than total retention period)", + "retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.", + "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", + "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", + "retention_comparison_error": "Total retention period for {{name}} can’t be lower than period after which data is moved to s3", + "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", + "retention_confirmation": "Are you sure you want to change the retention period?", + "retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces." + } } diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index a38d7e6c81..b812ae9132 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -7,6 +7,7 @@ import TextToolTip from 'components/TextToolTip'; import useFetch from 'hooks/useFetch'; import { find } from 'lodash-es'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { IDiskType } from 'types/api/disks/getDisks'; import { PayloadProps } from 'types/api/settings/getRetention'; @@ -19,8 +20,8 @@ import { } from './styles'; function GeneralSettings(): JSX.Element { + const { t } = useTranslation(); const [notifications, Element] = notification.useNotification(); - const [modal, setModal] = useState(false); const [postApiLoading, setPostApiLoading] = useState(false); @@ -82,12 +83,12 @@ function GeneralSettings(): JSX.Element { name: 'Metrics', retentionFields: [ { - name: 'Total Retention Period', + name: t('settings.total_retention_period'), value: metricsTotalRetentionPeriod, setValue: setMetricsTotalRetentionPeriod, }, { - name: `Move to S3\n(should be lower than total retention period)`, + name: t('settings.move_to_s3'), value: metricsS3RetentionPeriod, setValue: setMetricsS3RetentionPeriod, hide: !s3Enabled, @@ -98,12 +99,12 @@ function GeneralSettings(): JSX.Element { name: 'Traces', retentionFields: [ { - name: 'Total Retention Period', + name: t('settings.total_retention_period'), value: tracesTotalRetentionPeriod, setValue: setTracesTotalRetentionPeriod, }, { - name: `Move to S3\n(should be lower than total retention period)`, + name: t('settings.move_to_s3'), value: tracesS3RetentionPeriod, setValue: setTracesS3RetentionPeriod, hide: !s3Enabled, @@ -165,12 +166,13 @@ function GeneralSettings(): JSX.Element { notifications.success({ message: 'Success!', placement: 'topRight', - description: `Congrats. The retention periods for ${name} has been updated successfully.`, + + description: t('settings.retention_success_message', { name }), }); } else { notifications.error({ message: 'Error', - description: `There was an issue in changing the retention period for ${name}. Please try again or reach out to support@signoz.io`, + description: t('settings.retention_error_message', { name }), placement: 'topRight', }); } @@ -180,8 +182,7 @@ function GeneralSettings(): JSX.Element { } catch (error) { notifications.error({ message: 'Error', - description: - 'There was an issue in changing the retention period. Please try again or reach out to support@signoz.io', + description: t('settings.retention_failed_message'), placement: 'topRight', }); } @@ -191,10 +192,10 @@ function GeneralSettings(): JSX.Element { const [isDisabled, errorText] = useMemo(() => { // Various methods to return dynamic error message text. const messages = { - compareError: (value: string | number): string => - `Total retention period for ${value} can’t be lower than period after which data is moved to s3`, - nullValueError: (value: string | number): string => - `Retention Peroid for ${value} is not set yet. Please set by choosing below`, + compareError: (name: string | number): string => + t('settings.retention_comparison_error', { name }), + nullValueError: (name: string | number): string => + t('settings.retention_null_value_error', { name }), }; // Defaults to button not disabled and empty error message text. @@ -232,6 +233,7 @@ function GeneralSettings(): JSX.Element { metricsS3RetentionPeriod, metricsTotalRetentionPeriod, s3Enabled, + t, tracesS3RetentionPeriod, tracesTotalRetentionPeriod, ]); @@ -271,7 +273,7 @@ function GeneralSettings(): JSX.Element { {renderConfig} - - This will change the amount of storage needed for saving metrics & traces. - + {t('settings.retention_confirmation_description')} From 42842b6b17d495e2c43bd6a7f3bc839e52305491 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 15:51:38 +0530 Subject: [PATCH 029/154] feat: i18n support for setting route names --- frontend/public/locales/en/translation.json | 4 ++++ frontend/src/pages/AlertChannelCreate/index.tsx | 12 ++++++++---- frontend/src/pages/AllAlertChannels/index.tsx | 12 ++++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index e18493e548..1b90499381 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -1,5 +1,9 @@ { "monitor_signup": "Monitor your applications. Find what is causing issues.", + "routes": { + "general": "General", + "alert_channels": "Alert Channels" + }, "settings": { "total_retention_period": "Total Retention Period", "move_to_s3": "Move to S3\n(should be lower than total retention period)", diff --git a/frontend/src/pages/AlertChannelCreate/index.tsx b/frontend/src/pages/AlertChannelCreate/index.tsx index 3de6ce9986..ecc94247a4 100644 --- a/frontend/src/pages/AlertChannelCreate/index.tsx +++ b/frontend/src/pages/AlertChannelCreate/index.tsx @@ -5,28 +5,32 @@ import CreateAlertChannels from 'container/CreateAlertChannels'; import GeneralSettings from 'container/GeneralSettings'; import history from 'lib/history'; import React from 'react'; +import { useTranslation } from 'react-i18next'; function SettingsPage(): JSX.Element { const pathName = history.location.pathname; - + const { t } = useTranslation(); return ( { return ; }, - name: 'Alert Channels', + name: t('routes.alert_channels'), route: ROUTES.ALL_CHANNELS, }, ], - activeKey: pathName === ROUTES.SETTINGS ? 'General' : 'Alert Channels', + activeKey: + pathName === ROUTES.SETTINGS + ? t('routes.general') + : t('routes.alert_channels'), }} /> ); diff --git a/frontend/src/pages/AllAlertChannels/index.tsx b/frontend/src/pages/AllAlertChannels/index.tsx index b1d3820f66..1ab97d0db1 100644 --- a/frontend/src/pages/AllAlertChannels/index.tsx +++ b/frontend/src/pages/AllAlertChannels/index.tsx @@ -4,26 +4,30 @@ import AlertChannels from 'container/AllAlertChannels'; import GeneralSettings from 'container/GeneralSettings'; import history from 'lib/history'; import React from 'react'; +import { useTranslation } from 'react-i18next'; function AllAlertChannels(): JSX.Element { const pathName = history.location.pathname; - + const { t } = useTranslation(); return ( ); From eae53d9effff47ba97caef604d6ff7d5af8a4e8e Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 16:05:46 +0530 Subject: [PATCH 030/154] feat: condition changes --- frontend/public/locales/en/translation.json | 2 +- frontend/src/container/GeneralSettings/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 1b90499381..571ac5e43a 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -10,7 +10,7 @@ "retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.", "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", - "retention_comparison_error": "Total retention period for {{name}} can’t be lower than period after which data is moved to s3", + "retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.", "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", "retention_confirmation": "Are you sure you want to change the retention period?", "retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces." diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index b812ae9132..73d61550a9 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -205,13 +205,13 @@ function GeneralSettings(): JSX.Element { if (s3Enabled) { if ( (metricsTotalRetentionPeriod || metricsS3RetentionPeriod) && - Number(metricsTotalRetentionPeriod) < Number(metricsS3RetentionPeriod) + Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod) ) { isDisabled = true; errorText = messages.compareError('metrics'); } else if ( (tracesTotalRetentionPeriod || tracesS3RetentionPeriod) && - Number(tracesTotalRetentionPeriod) < Number(tracesS3RetentionPeriod) + Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod) ) { isDisabled = true; errorText = messages.compareError('traces'); From 3b3fd2b3a90277d3c9b0d44b7dce137f08f73a62 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 16:09:04 +0530 Subject: [PATCH 031/154] chore: update type --- frontend/src/api/disks/getDisks.ts | 4 ++-- frontend/src/types/api/disks/getDisks.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/disks/getDisks.ts b/frontend/src/api/disks/getDisks.ts index 70da1bddc9..9dced1b0fd 100644 --- a/frontend/src/api/disks/getDisks.ts +++ b/frontend/src/api/disks/getDisks.ts @@ -2,10 +2,10 @@ import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; -import { IDiskType } from 'types/api/disks/getDisks'; +import { PayloadProps } from 'types/api/disks/getDisks'; const getDisks = async (): Promise< - SuccessResponse | ErrorResponse + SuccessResponse | ErrorResponse > => { try { const response = await axios.get(`/disks`); diff --git a/frontend/src/types/api/disks/getDisks.ts b/frontend/src/types/api/disks/getDisks.ts index fc917db7d9..9e444df53d 100644 --- a/frontend/src/types/api/disks/getDisks.ts +++ b/frontend/src/types/api/disks/getDisks.ts @@ -1,3 +1,4 @@ +export type PayloadProps = IDiskType[]; export interface IDiskType { name: string; type: string; From 566c2becdf36f963aec4befac3f10bda508a6ebe Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 16:09:57 +0530 Subject: [PATCH 032/154] feat: dynamic step size for the data for graphs (#929) * feat: dynamic step size for the data for graphs * fix: remove console.log * chore: add jest globals * feat: add step size for dashboard * chore: undo .eslintignore --- frontend/.eslintignore | 2 +- frontend/src/lib/__tests__/getStep.test.ts | 5 +++-- frontend/src/lib/getStep.ts | 9 +++------ frontend/src/modules/Usage/UsageExplorer.tsx | 2 +- frontend/src/pages/Trace/index.tsx | 3 ++- frontend/src/store/actions/dashboard/getQueryResults.ts | 3 ++- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/.eslintignore b/frontend/.eslintignore index b7dab5e9cb..dd87e2d73f 100644 --- a/frontend/.eslintignore +++ b/frontend/.eslintignore @@ -1,2 +1,2 @@ node_modules -build \ No newline at end of file +build diff --git a/frontend/src/lib/__tests__/getStep.test.ts b/frontend/src/lib/__tests__/getStep.test.ts index 399f0cfbc6..d3eaab422c 100644 --- a/frontend/src/lib/__tests__/getStep.test.ts +++ b/frontend/src/lib/__tests__/getStep.test.ts @@ -1,6 +1,6 @@ import { expect } from '@jest/globals'; import dayjs from 'dayjs'; -import getStep, { DefaultStepSize } from 'lib/getStep'; +import getStep, { DefaultStepSize, MaxDataPoints } from 'lib/getStep'; describe('lib/getStep', () => { test('should return default step when the given range is less than 1 day', () => { @@ -40,7 +40,8 @@ describe('lib/getStep', () => { const startUnix = start.valueOf(); const endUnix = end.valueOf(); - const expectedStepSize = end.diff(start, 'days') * DefaultStepSize; + const expectedStepSize = Math.floor(end.diff(start, 's') / MaxDataPoints); + expect( getStep({ start: startUnix / 1e3, diff --git a/frontend/src/lib/getStep.ts b/frontend/src/lib/getStep.ts index c7388bbe06..347f9dc332 100644 --- a/frontend/src/lib/getStep.ts +++ b/frontend/src/lib/getStep.ts @@ -30,6 +30,7 @@ const convertToMs = ( }; export const DefaultStepSize = 60; +export const MaxDataPoints = 200; /** * Returns relevant step size based on given start and end date. @@ -37,13 +38,9 @@ export const DefaultStepSize = 60; 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')); + const diffSec = Math.abs(endDate.diff(startDate, 's')); - if (diffDays > 1) { - return DefaultStepSize * diffDays; - } - - return DefaultStepSize; + return Math.max(Math.floor(diffSec / MaxDataPoints), DefaultStepSize); }; export default getStep; diff --git a/frontend/src/modules/Usage/UsageExplorer.tsx b/frontend/src/modules/Usage/UsageExplorer.tsx index 502ae0294b..ec37b6ae33 100644 --- a/frontend/src/modules/Usage/UsageExplorer.tsx +++ b/frontend/src/modules/Usage/UsageExplorer.tsx @@ -84,7 +84,7 @@ function _UsageExplorer(props: UsageExplorerProps): JSX.Element { if (selectedTime && selectedInterval) { const maxTime = new Date().getTime() * 1000000; const minTime = maxTime - selectedTime.value * 24 * 3600000 * 1000000; - + getUsageData(minTime, maxTime, selectedInterval.value, selectedService); } }, [selectedTime, selectedInterval, selectedService, getUsageData]); diff --git a/frontend/src/pages/Trace/index.tsx b/frontend/src/pages/Trace/index.tsx index 427dd0d14e..9764c83afe 100644 --- a/frontend/src/pages/Trace/index.tsx +++ b/frontend/src/pages/Trace/index.tsx @@ -5,6 +5,7 @@ import TraceGraph from 'container/Trace/Graph'; import Search from 'container/Trace/Search'; import TraceGraphFilter from 'container/Trace/TraceGraphFilter'; import TraceTable from 'container/Trace/TraceTable'; +import getStep from 'lib/getStep'; import history from 'lib/history'; import React, { useCallback, useEffect, useState } from 'react'; import { connect, useDispatch, useSelector } from 'react-redux'; @@ -84,7 +85,7 @@ function Trace({ selectedFilter, selectedTags, start: minTime, - step: 60, + step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }), isFilterExclude, }); }, [ diff --git a/frontend/src/store/actions/dashboard/getQueryResults.ts b/frontend/src/store/actions/dashboard/getQueryResults.ts index b87aca985f..e2ced192db 100644 --- a/frontend/src/store/actions/dashboard/getQueryResults.ts +++ b/frontend/src/store/actions/dashboard/getQueryResults.ts @@ -5,6 +5,7 @@ import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems import GetMaxMinTime from 'lib/getMaxMinTime'; import GetMinMax from 'lib/getMinMax'; import GetStartAndEndTime from 'lib/getStartAndEndTime'; +import getStep from 'lib/getStep'; import { Dispatch } from 'redux'; import store from 'store'; import AppActions from 'types/actions'; @@ -45,7 +46,7 @@ export const GetQueryResults = ( end, query: encodeURIComponent(query.query), start, - step: '60', + step: `${getStep({ start, end, inputFormat: 'ms' })}`, }); return { query: query.query, From ccf352f2db27e3600907c25bbb5d4aacc2a4b0e9 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 5 Apr 2022 17:02:39 +0530 Subject: [PATCH 033/154] fix: error color for spans having error on trace detail page --- frontend/src/utils/getSpanTreeMetadata.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/utils/getSpanTreeMetadata.ts b/frontend/src/utils/getSpanTreeMetadata.ts index 3cce2667d2..d85480566d 100644 --- a/frontend/src/utils/getSpanTreeMetadata.ts +++ b/frontend/src/utils/getSpanTreeMetadata.ts @@ -25,8 +25,7 @@ export const getSpanTreeMetadata = ( globalEnd = Math.max(globalEnd, endTime); if (treeNode.hasError) { treeNode.serviceColour = errorColor; - } - treeNode.serviceColour = spanServiceColours[treeNode.serviceName]; + } else treeNode.serviceColour = spanServiceColours[treeNode.serviceName]; treeNode.children.forEach((childNode) => { traverse(childNode, level + 1); }); From 2a348e916caf2c213943c15b2b14f1548a234617 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Tue, 5 Apr 2022 18:21:25 +0530 Subject: [PATCH 034/154] feat: version page is added (#924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat👔 : getLatestVersion api is added * chore: VERSION page is added * feat: ✨ version page is added * chore: all string is grabbed from locale * chore: warning is removed * chore: translation json is added * chore: feedback about version is added * chore: made two different functions * unused import is removed * feat: version changes are updated * chore: if current version is present then it is displayed --- frontend/public/locales/en/translation.json | 9 ++ frontend/src/AppRoutes/pageComponents.ts | 4 + frontend/src/AppRoutes/routes.ts | 6 + frontend/src/api/user/getLatestVersion.ts | 25 ++++ frontend/src/constants/routes.ts | 1 + frontend/src/container/AppLayout/index.tsx | 92 ++++++++++++++- .../container/Header/Breadcrumbs/index.tsx | 1 + frontend/src/container/SideNav/index.tsx | 66 +++++++++-- frontend/src/container/SideNav/styles.ts | 26 ++++- frontend/src/container/Version/index.tsx | 110 ++++++++++++++++++ frontend/src/container/Version/styles.ts | 8 ++ frontend/src/pages/Status/index.tsx | 8 ++ frontend/src/store/reducers/app.ts | 30 +++++ frontend/src/types/actions/app.ts | 37 +++++- .../src/types/api/user/getLatestVersion.ts | 18 +++ frontend/src/types/reducer/app.ts | 4 + 16 files changed, 433 insertions(+), 12 deletions(-) create mode 100644 frontend/src/api/user/getLatestVersion.ts create mode 100644 frontend/src/container/Version/index.tsx create mode 100644 frontend/src/container/Version/styles.ts create mode 100644 frontend/src/pages/Status/index.tsx create mode 100644 frontend/src/types/api/user/getLatestVersion.ts diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 571ac5e43a..23330080e6 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -1,5 +1,14 @@ { "monitor_signup": "Monitor your applications. Find what is causing issues.", + "version": "Version", + "latest_version": "Latest version", + "current_version": "Current version", + "release_notes": "Release Notes", + "read_how_to_upgrade": "Read instructions on how to upgrade", + "latest_version_signoz": "You are running the latest version of SigNoz.", + "stale_version": "You are on an older version and may be loosing on the latest features we have shipped. We recommend to upgrade to the latest version", + "oops_something_went_wrong_version": "Oops.. facing issues with fetching updated version information", + "n_a": "N/A", "routes": { "general": "General", "alert_channels": "Alert Channels" diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index eb7430eb3c..25d4dc5886 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -85,3 +85,7 @@ export const EditAlertChannelsAlerts = Loadable( export const AllAlertChannels = Loadable( () => import(/* webpackChunkName: "All Channels" */ 'pages/AllAlertChannels'), ); + +export const StatusPage = Loadable( + () => import(/* webpackChunkName: "All Status" */ 'pages/Status'), +); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 474f71f510..10f5a1997a 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -17,6 +17,7 @@ import { ServicesTablePage, SettingsPage, SignupPage, + StatusPage, TraceDetail, TraceFilter, UsageExplorerPage, @@ -113,6 +114,11 @@ const routes: AppRoutes[] = [ exact: true, component: AllAlertChannels, }, + { + path: ROUTES.VERSION, + exact: true, + component: StatusPage, + }, ]; interface AppRoutes { diff --git a/frontend/src/api/user/getLatestVersion.ts b/frontend/src/api/user/getLatestVersion.ts new file mode 100644 index 0000000000..28a72f78be --- /dev/null +++ b/frontend/src/api/user/getLatestVersion.ts @@ -0,0 +1,25 @@ +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import axios, { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps } from 'types/api/user/getLatestVersion'; + +const getLatestVersion = async (): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.get( + `https://api.github.com/repos/signoz/signoz/releases/latest`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getLatestVersion; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index acd95003ea..e62cec59f2 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -17,6 +17,7 @@ const ROUTES = { ALL_CHANNELS: '/settings/channels', CHANNELS_NEW: '/setting/channels/new', CHANNELS_EDIT: '/setting/channels/edit/:id', + VERSION: '/status', }; export default ROUTES; diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 5230ca5bae..1fa47522c5 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,11 +1,24 @@ +import { notification } from 'antd'; +import getLatestVersion from 'api/user/getLatestVersion'; +import getVersion from 'api/user/getVersion'; import ROUTES from 'constants/routes'; import TopNav from 'container/Header'; import SideNav from 'container/SideNav'; +import useFetch from 'hooks/useFetch'; import history from 'lib/history'; -import React, { ReactNode, useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; +import { Dispatch } from 'redux'; import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { + UPDATE_CURRENT_ERROR, + UPDATE_CURRENT_VERSION, + UPDATE_LATEST_VERSION, + UPDATE_LATEST_VERSION_ERROR, +} from 'types/actions/app'; import AppReducer from 'types/reducer/app'; import { Content, Layout } from './styles'; @@ -13,11 +26,24 @@ import { Content, Layout } from './styles'; function AppLayout(props: AppLayoutProps): JSX.Element { const { isLoggedIn } = useSelector((state) => state.app); const { pathname } = useLocation(); + const { t } = useTranslation(); const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname); + const { payload: versionPayload, loading, error: getVersionError } = useFetch( + getVersion, + ); + + const { + payload: latestVersionPayload, + loading: latestLoading, + error: latestError, + } = useFetch(getLatestVersion); + const { children } = props; + const dispatch = useDispatch>(); + useEffect(() => { if (!isLoggedIn) { setIsSignUpPage(true); @@ -27,11 +53,71 @@ function AppLayout(props: AppLayoutProps): JSX.Element { } }, [isLoggedIn, isSignUpPage]); + const latestCurrentCounter = useRef(0); + const latestVersionCounter = useRef(0); + useEffect(() => { if (isLoggedIn && pathname === ROUTES.SIGN_UP) { history.push(ROUTES.APPLICATION); } - }, [isLoggedIn, pathname]); + + if (!latestLoading && latestError && latestCurrentCounter.current === 0) { + latestCurrentCounter.current = 1; + + dispatch({ + type: UPDATE_LATEST_VERSION_ERROR, + payload: { + isError: true, + }, + }); + notification.error({ + message: t('oops_something_went_wrong_version'), + }); + } + + if (!loading && getVersionError && latestVersionCounter.current === 0) { + latestVersionCounter.current = 1; + + dispatch({ + type: UPDATE_CURRENT_ERROR, + payload: { + isError: true, + }, + }); + notification.error({ + message: t('oops_something_went_wrong_version'), + }); + } + + if (!latestLoading && versionPayload) { + dispatch({ + type: UPDATE_CURRENT_VERSION, + payload: { + currentVersion: versionPayload.version, + }, + }); + } + + if (!loading && latestVersionPayload) { + dispatch({ + type: UPDATE_LATEST_VERSION, + payload: { + latestVersion: latestVersionPayload.name, + }, + }); + } + }, [ + dispatch, + loading, + latestLoading, + versionPayload, + latestVersionPayload, + isLoggedIn, + pathname, + getVersionError, + latestError, + t, + ]); return ( diff --git a/frontend/src/container/Header/Breadcrumbs/index.tsx b/frontend/src/container/Header/Breadcrumbs/index.tsx index f1c295e1ff..d88384b75c 100644 --- a/frontend/src/container/Header/Breadcrumbs/index.tsx +++ b/frontend/src/container/Header/Breadcrumbs/index.tsx @@ -11,6 +11,7 @@ const breadcrumbNameMap = { [ROUTES.INSTRUMENTATION]: 'Add instrumentation', [ROUTES.SETTINGS]: 'Settings', [ROUTES.DASHBOARD]: 'Dashboard', + [ROUTES.VERSION]: 'Status', }; function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element { diff --git a/frontend/src/container/SideNav/index.tsx b/frontend/src/container/SideNav/index.tsx index 4902986dbd..44bb2201e8 100644 --- a/frontend/src/container/SideNav/index.tsx +++ b/frontend/src/container/SideNav/index.tsx @@ -1,3 +1,4 @@ +import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons'; import { Menu, Typography } from 'antd'; import getLocalStorageKey from 'api/browser/localstorage/get'; import { IS_SIDEBAR_COLLAPSED } from 'constants/app'; @@ -5,6 +6,7 @@ import ROUTES from 'constants/routes'; import history from 'lib/history'; import setTheme, { AppMode } from 'lib/theme/setTheme'; import React, { useCallback, useLayoutEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { connect, useDispatch, useSelector } from 'react-redux'; import { NavLink, useLocation } from 'react-router-dom'; import { bindActionCreators } from 'redux'; @@ -19,11 +21,13 @@ import menus from './menuItems'; import Slack from './Slack'; import { Logo, + RedDot, Sider, SlackButton, SlackMenuItemContainer, ThemeSwitcherWrapper, ToggleButton, + VersionContainer, } from './styles'; function SideNav({ toggleDarkMode }: Props): JSX.Element { @@ -31,9 +35,15 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element { const [collapsed, setCollapsed] = useState( getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true', ); - const { isDarkMode } = useSelector((state) => state.app); + const { + isDarkMode, + currentVersion, + latestVersion, + isCurrentVersionError, + } = useSelector((state) => state.app); const { pathname } = useLocation(); + const { t } = useTranslation(''); const toggleTheme = useCallback(() => { const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode'; @@ -77,6 +87,38 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element { window.open('https://signoz.io/slack', '_blank'); }; + const onClickVersionHandler = (): void => { + history.push(ROUTES.VERSION); + }; + + const isNotCurrentVersion = currentVersion !== latestVersion; + + const sidebar = [ + { + onClick: onClickSlackHandler, + icon: , + text: Support, + }, + { + onClick: onClickVersionHandler, + icon: isNotCurrentVersion ? ( + + ) : ( + + ), + text: ( + + {!isCurrentVersionError ? ( + {currentVersion} + ) : ( + {t('n_a')} + )} + {isNotCurrentVersion && } + + ), + }, + ]; + return ( @@ -87,7 +129,7 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element { /> - + {name} ))} - - }> - Support - - + {sidebar.map((props, index) => ( + + + {props.text} + + + ))} ); diff --git a/frontend/src/container/SideNav/styles.ts b/frontend/src/container/SideNav/styles.ts index 0066d2affd..975aa65ba9 100644 --- a/frontend/src/container/SideNav/styles.ts +++ b/frontend/src/container/SideNav/styles.ts @@ -19,6 +19,7 @@ export const Logo = styled.img` interface LogoProps { collapsed: boolean; + index: number; } export const Sider = styled(SiderComponent)` @@ -50,9 +51,10 @@ export const SlackButton = styled(Typography)` export const SlackMenuItemContainer = styled.div` position: fixed; - bottom: 48px; + bottom: ${({ index }): string => `${index * 48 + (index + 16)}px`}; background: #262626; width: ${({ collapsed }): string => (!collapsed ? '200px' : '80px')}; + transition: inherit; &&& { li { @@ -60,11 +62,14 @@ export const SlackMenuItemContainer = styled.div` collapsed && css` padding-left: 24px; + padding-top: 6px; `} } svg { margin-left: ${({ collapsed }): string => (collapsed ? '0' : '24px')}; + width: 28px; + height: 28px; ${({ collapsed }): StyledCSS => collapsed && @@ -73,5 +78,24 @@ export const SlackMenuItemContainer = styled.div` margin: 0 auto; `} } + .ant-menu-title-content { + margin: 0; + } + } +`; + +export const RedDot = styled.div` + width: 12px; + height: 12px; + background: #d32029; + border-radius: 50%; + + margin-left: 1rem; + margin-top: 0.5rem; +`; + +export const VersionContainer = styled.div` + &&& { + display: flex; } `; diff --git a/frontend/src/container/Version/index.tsx b/frontend/src/container/Version/index.tsx new file mode 100644 index 0000000000..e36a0c6d18 --- /dev/null +++ b/frontend/src/container/Version/index.tsx @@ -0,0 +1,110 @@ +import { WarningFilled } from '@ant-design/icons'; +import { Button, Card, Form, Space, Typography } from 'antd'; +import React, { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +import { InputComponent } from './styles'; + +const { Title } = Typography; + +function Version(): JSX.Element { + const [form] = Form.useForm(); + const { t } = useTranslation(); + + const onClickUpgradeHandler = useCallback((link: string) => { + window.open(link, '_blank'); + }, []); + + const { + currentVersion, + latestVersion, + isCurrentVersionError, + isLatestVersionError, + } = useSelector((state) => state.app); + + const isLatestVersion = currentVersion === latestVersion; + const isError = isCurrentVersionError || isLatestVersionError; + + return ( + + + {t('version')} + + +
+ + + + + + + + + + + {!isError && isLatestVersion && ( +
+ + + + {t('latest_version_signoz')} + + +
+ )} + + {!isError && !isLatestVersion && ( +
+ + + + + {t('stale_version')} + +
+ )} + + {!isError && !isLatestVersion && ( + + )} +
+ ); +} + +export default Version; diff --git a/frontend/src/container/Version/styles.ts b/frontend/src/container/Version/styles.ts new file mode 100644 index 0000000000..42c7bb1477 --- /dev/null +++ b/frontend/src/container/Version/styles.ts @@ -0,0 +1,8 @@ +import { Input } from 'antd'; +import styled from 'styled-components'; + +export const InputComponent = styled(Input)` + &&& { + max-width: 183px; + } +`; diff --git a/frontend/src/pages/Status/index.tsx b/frontend/src/pages/Status/index.tsx new file mode 100644 index 0000000000..f923b428b4 --- /dev/null +++ b/frontend/src/pages/Status/index.tsx @@ -0,0 +1,8 @@ +import Version from 'container/Version'; +import React from 'react'; + +function Status(): JSX.Element { + return ; +} + +export default Status; diff --git a/frontend/src/store/reducers/app.ts b/frontend/src/store/reducers/app.ts index 2f45bee614..a8feece5a4 100644 --- a/frontend/src/store/reducers/app.ts +++ b/frontend/src/store/reducers/app.ts @@ -7,6 +7,10 @@ import { LOGGED_IN, SIDEBAR_COLLAPSE, SWITCH_DARK_MODE, + UPDATE_CURRENT_ERROR, + UPDATE_CURRENT_VERSION, + UPDATE_LATEST_VERSION, + UPDATE_LATEST_VERSION_ERROR, } from 'types/actions/app'; import InitialValueTypes from 'types/reducer/app'; @@ -14,6 +18,10 @@ const InitialValue: InitialValueTypes = { isDarkMode: getTheme() === 'darkMode', isLoggedIn: getLocalStorageKey(IS_LOGGED_IN) === 'yes', isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true', + currentVersion: '', + latestVersion: '', + isCurrentVersionError: false, + isLatestVersionError: false, }; const appReducer = ( @@ -42,6 +50,28 @@ const appReducer = ( }; } + case UPDATE_CURRENT_VERSION: { + return { + ...state, + currentVersion: action.payload.currentVersion, + }; + } + + case UPDATE_LATEST_VERSION: { + return { ...state, latestVersion: action.payload.latestVersion }; + } + + case UPDATE_CURRENT_ERROR: { + return { ...state, isCurrentVersionError: true }; + } + + case UPDATE_LATEST_VERSION_ERROR: { + return { + ...state, + isLatestVersionError: true, + }; + } + default: return state; } diff --git a/frontend/src/types/actions/app.ts b/frontend/src/types/actions/app.ts index f58b589a3d..b07cde36d4 100644 --- a/frontend/src/types/actions/app.ts +++ b/frontend/src/types/actions/app.ts @@ -1,7 +1,15 @@ +import AppReducer from 'types/reducer/app'; + export const SWITCH_DARK_MODE = 'SWITCH_DARK_MODE'; export const LOGGED_IN = 'LOGGED_IN'; export const SIDEBAR_COLLAPSE = 'SIDEBAR_COLLAPSE'; +export const UPDATE_CURRENT_VERSION = 'UPDATE_CURRENT_VERSION'; +export const UPDATE_LATEST_VERSION = 'UPDATE_LATEST_VERSION'; + +export const UPDATE_CURRENT_ERROR = 'UPDATE_CURRENT_ERROR'; +export const UPDATE_LATEST_VERSION_ERROR = 'UPDATE_LATEST_VERSION_ERROR'; + export interface SwitchDarkMode { type: typeof SWITCH_DARK_MODE; } @@ -15,4 +23,31 @@ export interface SideBarCollapse { payload: boolean; } -export type AppAction = SwitchDarkMode | LoggedInUser | SideBarCollapse; +export interface UpdateAppVersion { + type: typeof UPDATE_CURRENT_VERSION; + payload: { + currentVersion: AppReducer['currentVersion']; + }; +} + +export interface UpdateLatestVersion { + type: typeof UPDATE_LATEST_VERSION; + payload: { + latestVersion: AppReducer['latestVersion']; + }; +} + +export interface UpdateVersionError { + type: typeof UPDATE_CURRENT_ERROR | typeof UPDATE_LATEST_VERSION_ERROR; + payload: { + isError: boolean; + }; +} + +export type AppAction = + | SwitchDarkMode + | LoggedInUser + | SideBarCollapse + | UpdateAppVersion + | UpdateLatestVersion + | UpdateVersionError; diff --git a/frontend/src/types/api/user/getLatestVersion.ts b/frontend/src/types/api/user/getLatestVersion.ts new file mode 100644 index 0000000000..043ccb1b45 --- /dev/null +++ b/frontend/src/types/api/user/getLatestVersion.ts @@ -0,0 +1,18 @@ +export interface PayloadProps { + body: string; + created_at: string; + draft: boolean; + html_url: string; + id: number; + mentions_count: number; + name: string; + node_id: number; + prerelease: boolean; + published_at: string; + tag_name: number; + tarball_url: string; + target_commitish: string; + upload_url: string; + url: string; + zipball_url: string; +} diff --git a/frontend/src/types/reducer/app.ts b/frontend/src/types/reducer/app.ts index 36fa886ec9..68af65d075 100644 --- a/frontend/src/types/reducer/app.ts +++ b/frontend/src/types/reducer/app.ts @@ -2,4 +2,8 @@ export default interface AppReducer { isDarkMode: boolean; isLoggedIn: boolean; isSideBarCollapsed: boolean; + currentVersion: string; + latestVersion: string; + isCurrentVersionError: boolean; + isLatestVersionError: boolean; } From 064c3e044960770fcba7e23ff820fc0dfb88aac6 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Tue, 5 Apr 2022 23:13:12 +0530 Subject: [PATCH 035/154] chore: default text is updated (#954) * text and title is updated --- frontend/src/container/CreateAlertChannels/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/container/CreateAlertChannels/index.tsx b/frontend/src/container/CreateAlertChannels/index.tsx index f999627154..02cd7b274a 100644 --- a/frontend/src/container/CreateAlertChannels/index.tsx +++ b/frontend/src/container/CreateAlertChannels/index.tsx @@ -18,12 +18,14 @@ function CreateAlertChannels({ preType = 'slack', }: CreateAlertChannelsProps): JSX.Element { const [formInstance] = Form.useForm(); + const [selectedConfig, setSelectedConfig] = useState< Partial >({ - text: ` {{ range .Alerts -}} - *Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }} + text: `{{ range .Alerts -}} + *Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }} + *Summary:* {{ .Annotations.summary }} *Description:* {{ .Annotations.description }} *Details:* From 8809105a8d4e8253d91b44e5fa343d30e513cad5 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Wed, 6 Apr 2022 00:05:05 +0530 Subject: [PATCH 036/154] Prashant/deploy changes (#955) - set information log level in clickhouse logger config - maximum logs size 150m (3 files each of 50m) Signed-off-by: Prashant Shahi --- Makefile | 6 +- .../clickhouse-setup/clickhouse-config.xml | 35 ++++++++++-- .../clickhouse-setup/docker-compose.yaml | 12 ++++ .../clickhouse-setup/clickhouse-config.xml | 56 +++++++++---------- .../clickhouse-setup/docker-compose.arm.yaml | 16 +++++- .../clickhouse-setup/docker-compose.yaml | 16 +++++- .../tests/test-deploy/clickhouse-config.xml | 7 +-- .../tests/test-deploy/docker-compose.arm.yaml | 11 ++++ .../tests/test-deploy/docker-compose.yaml | 11 ++++ 9 files changed, 125 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index bf2b038992..8dc880a971 100644 --- a/Makefile +++ b/Makefile @@ -115,11 +115,9 @@ down-arm: @docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.arm.yaml down -v clear-standalone-data: - @cd $(STANDALONE_DIRECTORY) - @docker run --rm -v "data:/pwd" busybox \ + @docker run --rm -v "$(PWD)/$(STANDALONE_DIRECTORY)/data:/pwd" busybox \ sh -c "cd /pwd && rm -rf alertmanager/* clickhouse/* signoz/*" clear-swarm-data: - @cd $(SWARM_DIRECTORY) - @docker run --rm -v "data:/pwd" busybox \ + @docker run --rm -v "$(PWD)/$(SWARM_DIRECTORY)/data:/pwd" busybox \ sh -c "cd /pwd && rm -rf alertmanager/* clickhouse/* signoz/*" diff --git a/deploy/docker-swarm/clickhouse-setup/clickhouse-config.xml b/deploy/docker-swarm/clickhouse-setup/clickhouse-config.xml index 23898ef5e7..7a5f40d299 100644 --- a/deploy/docker-swarm/clickhouse-setup/clickhouse-config.xml +++ b/deploy/docker-swarm/clickhouse-setup/clickhouse-config.xml @@ -1,11 +1,8 @@ - trace - /var/log/clickhouse-server/clickhouse-server.log - /var/log/clickhouse-server/clickhouse-server.err.log - 1000M - 10 + information + 1 8123 @@ -45,6 +42,34 @@ + + + + - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml index 0540fa2868..3b25560153 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml @@ -3,10 +3,17 @@ version: "2.4" services: clickhouse: image: altinity/clickhouse-server:21.12.3.32.altinitydev.arm + # ports: + # - "9000:9000" + # - "8123:8123" volumes: - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml - ./data/clickhouse/:/var/lib/clickhouse/ restart: on-failure + logging: + options: + max-size: 50m + max-file: "3" healthcheck: # "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'" test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"] @@ -19,7 +26,9 @@ services: volumes: - ./data/alertmanager:/data depends_on: - - query-service + query-service: + condition: service_healthy + restart: on-failure command: - --queryService.url=http://query-service:8080 - --storage.path=/data @@ -40,6 +49,11 @@ services: - DEPLOYMENT_TYPE=docker-standalone-arm restart: on-failure + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"] + interval: 30s + timeout: 5s + retries: 3 depends_on: clickhouse: condition: service_healthy diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index b9c96c7bdb..2493eea176 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -3,10 +3,17 @@ version: "2.4" services: clickhouse: image: yandex/clickhouse-server:21.12.3.32 + # ports: + # - "9000:9000" + # - "8123:8123" volumes: - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml - ./data/clickhouse/:/var/lib/clickhouse/ restart: on-failure + logging: + options: + max-size: 50m + max-file: "3" healthcheck: # "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'" test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"] @@ -19,7 +26,9 @@ services: volumes: - ./data/alertmanager:/data depends_on: - - query-service + query-service: + condition: service_healthy + restart: on-failure command: - --queryService.url=http://query-service:8080 - --storage.path=/data @@ -41,6 +50,11 @@ services: - TELEMETRY_ENABLED=true - DEPLOYMENT_TYPE=docker-standalone-amd restart: on-failure + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"] + interval: 30s + timeout: 5s + retries: 3 depends_on: clickhouse: condition: service_healthy diff --git a/pkg/query-service/tests/test-deploy/clickhouse-config.xml b/pkg/query-service/tests/test-deploy/clickhouse-config.xml index a955a6a3fa..06ddb2b723 100644 --- a/pkg/query-service/tests/test-deploy/clickhouse-config.xml +++ b/pkg/query-service/tests/test-deploy/clickhouse-config.xml @@ -1,11 +1,8 @@ - trace - /var/log/clickhouse-server/clickhouse-server.log - /var/log/clickhouse-server/clickhouse-server.err.log - 1000M - 10 + information + 1 8123 diff --git a/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml b/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml index 0447c26a35..a5d4e28796 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml @@ -5,6 +5,11 @@ services: image: altinity/clickhouse-server:21.12.3.32.altinitydev.arm volumes: - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml + restart: on-failure + logging: + options: + max-size: 50m + max-file: "3" healthcheck: # "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'" test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"] @@ -16,6 +21,7 @@ services: image: signoz/alertmanager:0.6.0 depends_on: - query-service + restart: on-failure command: - --queryService.url=http://query-service:8080 - --storage.path=/data @@ -35,6 +41,11 @@ services: - STORAGE=clickhouse - GODEBUG=netdns=go - TELEMETRY_ENABLED=true + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"] + interval: 30s + timeout: 5s + retries: 3 depends_on: clickhouse: condition: service_healthy diff --git a/pkg/query-service/tests/test-deploy/docker-compose.yaml b/pkg/query-service/tests/test-deploy/docker-compose.yaml index 6edd31b63a..c88e3544ec 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.yaml @@ -5,6 +5,11 @@ services: image: yandex/clickhouse-server:21.12.3.32 volumes: - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml + restart: on-failure + logging: + options: + max-size: 50m + max-file: "3" healthcheck: # "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'" test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"] @@ -16,6 +21,7 @@ services: image: signoz/alertmanager:0.6.0 depends_on: - query-service + restart: on-failure command: - --queryService.url=http://query-service:8080 - --storage.path=/data @@ -37,6 +43,11 @@ services: - STORAGE=clickhouse - GODEBUG=netdns=go - TELEMETRY_ENABLED=true + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"] + interval: 30s + timeout: 5s + retries: 3 depends_on: clickhouse: condition: service_healthy From 1e522ad8f122e64436d4eaa648f84e317951ec67 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Wed, 6 Apr 2022 00:19:13 +0530 Subject: [PATCH 037/154] =?UTF-8?q?chore(release):=20=F0=9F=93=8C=20pin=20?= =?UTF-8?q?0.7.5=20SigNoz=20version=20and=200.6.1=20Alertmanager=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 6 +++--- deploy/docker/clickhouse-setup/docker-compose.arm.yaml | 6 +++--- deploy/docker/clickhouse-setup/docker-compose.yaml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 36704c4698..2168c3cc53 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -24,7 +24,7 @@ services: retries: 3 alertmanager: - image: signoz/alertmanager:0.6.0 + image: signoz/alertmanager:0.6.1 volumes: - ./data/alertmanager:/data command: @@ -37,7 +37,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.7.4 + image: signoz/query-service:0.7.5 command: ["-config=/root/config/prometheus.yml"] ports: - "8080:8080" @@ -64,7 +64,7 @@ services: - clickhouse frontend: - image: signoz/frontend:0.7.4 + image: signoz/frontend:0.7.5 depends_on: - query-service ports: diff --git a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml index 3b25560153..86d6f67fd4 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml @@ -22,7 +22,7 @@ services: retries: 3 alertmanager: - image: signoz/alertmanager:0.6.0 + image: signoz/alertmanager:0.6.1 volumes: - ./data/alertmanager:/data depends_on: @@ -34,7 +34,7 @@ services: - --storage.path=/data query-service: - image: signoz/query-service:0.7.4 + image: signoz/query-service:0.7.5 container_name: query-service command: ["-config=/root/config/prometheus.yml"] volumes: @@ -59,7 +59,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.7.4 + image: signoz/frontend:0.7.5 container_name: frontend depends_on: - query-service diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 2493eea176..5a930a5fe9 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -22,7 +22,7 @@ services: retries: 3 alertmanager: - image: signoz/alertmanager:0.6.0 + image: signoz/alertmanager:0.6.1 volumes: - ./data/alertmanager:/data depends_on: @@ -36,7 +36,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:0.7.4 + image: signoz/query-service:0.7.5 container_name: query-service command: ["-config=/root/config/prometheus.yml"] volumes: @@ -60,7 +60,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.7.4 + image: signoz/frontend:0.7.5 container_name: frontend depends_on: - query-service From 58daca1579e60d4363a296efc611f033b25f8ee7 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Wed, 6 Apr 2022 00:52:48 +0530 Subject: [PATCH 038/154] =?UTF-8?q?chore(docker-compose):=20=F0=9F=94=A7?= =?UTF-8?q?=20add=20restart=20policy=20and=20frontend=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 4 ++++ deploy/docker/clickhouse-setup/docker-compose.arm.yaml | 2 ++ deploy/docker/clickhouse-setup/docker-compose.yaml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 2168c3cc53..6c7010963b 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -65,7 +65,11 @@ services: frontend: image: signoz/frontend:0.7.5 + deploy: + restart_policy: + condition: on-failure depends_on: + - alertmanager - query-service ports: - "3301:3301" diff --git a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml index 86d6f67fd4..9b4f86bc05 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml @@ -61,7 +61,9 @@ services: frontend: image: signoz/frontend:0.7.5 container_name: frontend + restart: on-failure depends_on: + - alertmanager - query-service ports: - "3301:3301" diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 5a930a5fe9..1d7fa921e7 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -62,7 +62,9 @@ services: frontend: image: signoz/frontend:0.7.5 container_name: frontend + restart: on-failure depends_on: + - alertmanager - query-service ports: - "3301:3301" From 1d6ddd48908453d77df8364cd26b39bd8ae4fb21 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Wed, 6 Apr 2022 01:37:07 +0530 Subject: [PATCH 039/154] =?UTF-8?q?chore(install-script):=20=F0=9F=A9=B9?= =?UTF-8?q?=20fix=20email=20condition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/install.sh b/deploy/install.sh index 569394bee5..abca7f0878 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -247,7 +247,7 @@ bye() { # Prints a friendly good bye message and exits the script. echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack" echo "++++++++++++++++++++++++++++++++++++++++" - if [[ email == "" ]]; then + if [[ $email == "" ]]; then echo -e "\n📨 Please share your email to receive support with the installation" read -rp 'Email: ' email From d72dacdc1f87e376ec2b1bf0a6ea2d40a0adf093 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Wed, 6 Apr 2022 10:52:46 +0530 Subject: [PATCH 040/154] bug: current page is updated in redux to see the updated values --- frontend/src/container/Trace/TraceTable/index.tsx | 12 +++++++++++- frontend/src/store/reducers/trace.ts | 11 +++++++++++ frontend/src/types/actions/trace.ts | 12 +++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index b78d709b4f..8115c8ee64 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -10,7 +10,10 @@ import { Dispatch } from 'redux'; import { updateURL } from 'store/actions/trace/util'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; -import { UPDATE_SPAN_ORDER } from 'types/actions/trace'; +import { + UPDATE_SPAN_ORDER, + UPDATE_SPANS_AGGREGATE_PAGE_NUMBER, +} from 'types/actions/trace'; import { TraceReducer } from 'types/reducer/trace'; dayjs.extend(duration); @@ -136,6 +139,13 @@ function TraceTable(): JSX.Element { }, }); + dispatch({ + type: UPDATE_SPANS_AGGREGATE_PAGE_NUMBER, + payload: { + currentPage: props.current, + }, + }); + updateURL( selectedFilter, filterToFetchData, diff --git a/frontend/src/store/reducers/trace.ts b/frontend/src/store/reducers/trace.ts index 46328c4858..010813c16e 100644 --- a/frontend/src/store/reducers/trace.ts +++ b/frontend/src/store/reducers/trace.ts @@ -11,6 +11,7 @@ import { UPDATE_SELECTED_TAGS, UPDATE_SPAN_ORDER, UPDATE_SPANS_AGGREGATE, + UPDATE_SPANS_AGGREGATE_PAGE_NUMBER, UPDATE_TAG_MODAL_VISIBILITY, UPDATE_TRACE_FILTER, UPDATE_TRACE_FILTER_LOADING, @@ -213,6 +214,16 @@ const traceReducer = ( }; } + case UPDATE_SPANS_AGGREGATE_PAGE_NUMBER: { + return { + ...state, + spansAggregate: { + ...state.spansAggregate, + currentPage: action.payload.currentPage, + }, + }; + } + default: return state; } diff --git a/frontend/src/types/actions/trace.ts b/frontend/src/types/actions/trace.ts index c6f1f12bba..cfdb86bcbb 100644 --- a/frontend/src/types/actions/trace.ts +++ b/frontend/src/types/actions/trace.ts @@ -26,6 +26,8 @@ export const UPDATE_FILTER_RESPONSE_SELECTED = export const UPDATE_FILTER_EXCLUDE = 'UPDATE_FILTER_EXCLUDE'; export const UPDATE_SPAN_ORDER = 'UPDATE_SPAN_ORDER'; +export const UPDATE_SPANS_AGGREGATE_PAGE_NUMBER = + 'UPDATE_SPANS_AGGREGATE_PAGE_NUMBER'; export interface UpdateFilter { type: typeof UPDATE_TRACE_FILTER; @@ -152,6 +154,13 @@ export interface UpdateSpans { }; } +export interface UpdateSpansAggregatePageNumber { + type: typeof UPDATE_SPANS_AGGREGATE_PAGE_NUMBER; + payload: { + currentPage: TraceReducer['spansAggregate']['currentPage']; + }; +} + export interface UpdateSpanOrder { type: typeof UPDATE_SPAN_ORDER; payload: { @@ -177,4 +186,5 @@ export type TraceActions = | ResetTraceFilter | UpdateSelected | UpdateFilterExclude - | UpdateSpanOrder; + | UpdateSpanOrder + | UpdateSpansAggregatePageNumber; From ead6885b29b71eaebbb7ea4b38ce955e6ca19653 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Wed, 6 Apr 2022 11:33:56 +0530 Subject: [PATCH 041/154] bug: style-trace-search is fixed --- .../container/Trace/Search/AllTags/styles.ts | 2 +- frontend/src/container/Trace/Search/index.tsx | 62 +++++++++---------- frontend/src/container/Trace/Search/styles.ts | 1 + 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/frontend/src/container/Trace/Search/AllTags/styles.ts b/frontend/src/container/Trace/Search/AllTags/styles.ts index c43b32c5ea..1f8bc432ed 100644 --- a/frontend/src/container/Trace/Search/AllTags/styles.ts +++ b/frontend/src/container/Trace/Search/AllTags/styles.ts @@ -6,7 +6,7 @@ export const Container = styled(Card)` min-height: 20vh; width: 100%; z-index: 2; - position: absolute; + position: absolute !important; .ant-card-body { padding-bottom: 0; diff --git a/frontend/src/container/Trace/Search/index.tsx b/frontend/src/container/Trace/Search/index.tsx index 1590fd74b2..abe96a889c 100644 --- a/frontend/src/container/Trace/Search/index.tsx +++ b/frontend/src/container/Trace/Search/index.tsx @@ -113,41 +113,39 @@ function Search({ }; return ( - - - onChangeHandler(event.target.value)} - value={value} - allowClear - disabled={traces.filterLoading} - onFocus={onFocusHandler} - placeholder="Click to filter by tags" - type="search" - enterButton={} - onSearch={(string): void => { - if (string.length === 0) { - updateTagVisibility(false); - updateFilters([]); - return; - } + + onChangeHandler(event.target.value)} + value={value} + allowClear + disabled={traces.filterLoading} + onFocus={onFocusHandler} + placeholder="Click to filter by tags" + type="search" + enterButton={} + onSearch={(string): void => { + if (string.length === 0) { + updateTagVisibility(false); + updateFilters([]); + return; + } - const { isError, payload } = parseQueryToTags(string); + const { isError, payload } = parseQueryToTags(string); - if (isError) { - updateTagIsError(true); - } else { - updateTagIsError(false); - updateTagVisibility(false); - updateFilters(payload); - } - }} - /> + if (isError) { + updateTagIsError(true); + } else { + updateTagIsError(false); + updateTagVisibility(false); + updateFilters(payload); + } + }} + /> - {traces.isTagModalOpen && ( - - )} - - + {traces.isTagModalOpen && ( + + )} + ); } diff --git a/frontend/src/container/Trace/Search/styles.ts b/frontend/src/container/Trace/Search/styles.ts index 9cbec7a213..5ade20596d 100644 --- a/frontend/src/container/Trace/Search/styles.ts +++ b/frontend/src/container/Trace/Search/styles.ts @@ -6,6 +6,7 @@ const { Search } = Input; export const Container = styled.div` display: flex; position: relative; + width: 100%; `; export const SearchComponent = styled(Search)` From d0846b8dd2633103ddaa0cebc9417fb631a9506b Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Wed, 6 Apr 2022 12:40:14 +0530 Subject: [PATCH 042/154] fix: general setting TTL enhancements --- .../src/container/GeneralSettings/index.tsx | 161 +++++++++++------- .../src/container/GeneralSettings/styles.ts | 1 + 2 files changed, 98 insertions(+), 64 deletions(-) diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index 73d61550a9..6a76282192 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/cognitive-complexity */ import { Button, Col, Modal, notification, Row, Typography } from 'antd'; import getDisks from 'api/disks/getDisks'; import getRetentionPeriodApi from 'api/settings/getRetention'; @@ -12,12 +13,7 @@ import { IDiskType } from 'types/api/disks/getDisks'; import { PayloadProps } from 'types/api/settings/getRetention'; import Retention from './Retention'; -import { - ButtonContainer, - ErrorText, - ErrorTextContainer, - ToolTipContainer, -} from './styles'; +import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles'; function GeneralSettings(): JSX.Element { const { t } = useTranslation(); @@ -31,10 +27,17 @@ function GeneralSettings(): JSX.Element { getDisks().then((response) => setAvailableDisks(response.payload)); }, []); - const { payload: currentTTLValues, loading, error, errorMessage } = useFetch< + const { payload, loading, error, errorMessage } = useFetch< PayloadProps, undefined >(getRetentionPeriodApi, undefined); + + const [currentTTLValues, setCurrentTTLValues] = useState(payload); + + useEffect(() => { + setCurrentTTLValues(payload); + }, [payload]); + const [metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod] = useState< number | null >(null); @@ -63,6 +66,7 @@ function GeneralSettings(): JSX.Element { : null, ); } + console.log({ changed: currentTTLValues }); }, [currentTTLValues]); const onModalToggleHandler = (): void => { @@ -138,43 +142,65 @@ function GeneralSettings(): JSX.Element { const onOkHandler = async (): Promise => { try { setPostApiLoading(true); - const [metricsTTLApiResponse, tracesTTLApiResponse] = await Promise.all([ - setRetentionApi({ - type: 'metrics', - totalDuration: `${metricsTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${metricsS3RetentionPeriod || -1}h`, - }), - setRetentionApi({ - type: 'traces', - totalDuration: `${tracesTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${tracesS3RetentionPeriod || -1}h`, - }), - ]); - [ - { - apiResponse: metricsTTLApiResponse, - name: 'metrics', - }, - { - apiResponse: tracesTTLApiResponse, - name: 'traces', - }, - ].forEach(({ apiResponse, name }) => { - if (apiResponse.statusCode === 200) { - notifications.success({ - message: 'Success!', - placement: 'topRight', + const apiCalls = []; - description: t('settings.retention_success_message', { name }), - }); - } else { - notifications.error({ - message: 'Error', - description: t('settings.retention_error_message', { name }), - placement: 'topRight', - }); + if ( + !( + currentTTLValues?.metrics_move_ttl_duration_hrs === + metricsS3RetentionPeriod && + currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod + ) + ) { + apiCalls.push(() => + setRetentionApi({ + type: 'metrics', + totalDuration: `${metricsTotalRetentionPeriod || -1}h`, + coldStorage: s3Enabled ? 's3' : null, + toColdDuration: `${metricsS3RetentionPeriod || -1}h`, + }), + ); + } else { + apiCalls.push(() => Promise.resolve(null)); + } + + if ( + !( + currentTTLValues?.traces_move_ttl_duration_hrs === + tracesS3RetentionPeriod && + currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod + ) + ) { + apiCalls.push(() => + setRetentionApi({ + type: 'traces', + totalDuration: `${tracesTotalRetentionPeriod || -1}h`, + coldStorage: s3Enabled ? 's3' : null, + toColdDuration: `${tracesS3RetentionPeriod || -1}h`, + }), + ); + } else { + apiCalls.push(() => Promise.resolve(null)); + } + const apiCallSequence = ['metrics', 'traces']; + const apiResponses = await Promise.all(apiCalls.map((api) => api())); + + apiResponses.forEach((apiResponse, idx) => { + const name = apiCallSequence[idx]; + if (apiResponse) { + if (apiResponse.statusCode === 200) { + notifications.success({ + message: 'Success!', + placement: 'topRight', + + description: t('settings.retention_success_message', { name }), + }); + } else { + notifications.error({ + message: 'Error', + description: t('settings.retention_error_message', { name }), + placement: 'topRight', + }); + } } }); onModalToggleHandler(); @@ -186,10 +212,18 @@ function GeneralSettings(): JSX.Element { placement: 'topRight', }); } + // Updates the currentTTL Values in order to avoid pushing the same values. + setCurrentTTLValues({ + metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, + metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, + traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, + traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, + }); + setModal(false); }; - const [isDisabled, errorText] = useMemo(() => { + const [isDisabled, errorText] = useMemo((): [boolean, string] => { // Various methods to return dynamic error message text. const messages = { compareError: (name: string | number): string => @@ -228,8 +262,18 @@ function GeneralSettings(): JSX.Element { errorText = messages.nullValueError('traces'); } } + if ( + currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod && + currentTTLValues.metrics_move_ttl_duration_hrs === + metricsS3RetentionPeriod && + currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod && + currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod + ) { + isDisabled = true; + } return [isDisabled, errorText]; }, [ + currentTTLValues, metricsS3RetentionPeriod, metricsTotalRetentionPeriod, s3Enabled, @@ -249,27 +293,16 @@ function GeneralSettings(): JSX.Element { return (
{Element} - {errorText ? ( - - {errorText} + + + {errorText && {errorText}} + - - - ) : ( - - - - )} {renderConfig} article { margin-right: 1rem; From 7150971dc0650a9d58ebbc4e266fe1045b77d12f Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Wed, 6 Apr 2022 16:28:39 +0530 Subject: [PATCH 043/154] chore: style is updated --- .../container/Trace/Search/AllTags/index.tsx | 18 ++++++++++-------- .../container/Trace/Search/AllTags/styles.ts | 14 +++++--------- frontend/src/container/Trace/Search/index.tsx | 1 - 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/frontend/src/container/Trace/Search/AllTags/index.tsx b/frontend/src/container/Trace/Search/AllTags/index.tsx index 693f3c8451..fc132eafe8 100644 --- a/frontend/src/container/Trace/Search/AllTags/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/index.tsx @@ -116,14 +116,16 @@ function AllTags({ - - + + + + ); diff --git a/frontend/src/container/Trace/Search/AllTags/styles.ts b/frontend/src/container/Trace/Search/AllTags/styles.ts index 1f8bc432ed..3ce74315e1 100644 --- a/frontend/src/container/Trace/Search/AllTags/styles.ts +++ b/frontend/src/container/Trace/Search/AllTags/styles.ts @@ -35,20 +35,16 @@ export const Wrapper = styled.div` } `; -export const ButtonContainer = styled.div` +export const ButtonContainer = styled(Card)` display: flex; justify-content: flex-end; align-items: center; - background-color: #303030; - padding-top: 11px; - padding-bottom: 11px; - padding-right: 38.01px; - margin-top: 1rem; + padding-top: 11px !important; + padding-bottom: 11px !important; + padding-right: 38.01px !important; - > button:nth-child(1) { - margin-right: 1rem; - } + margin-top: 1rem !important; `; export const CurrentTagsContainer = styled.div` diff --git a/frontend/src/container/Trace/Search/index.tsx b/frontend/src/container/Trace/Search/index.tsx index abe96a889c..776a36efaf 100644 --- a/frontend/src/container/Trace/Search/index.tsx +++ b/frontend/src/container/Trace/Search/index.tsx @@ -1,5 +1,4 @@ import { CaretRightFilled } from '@ant-design/icons'; -import { Space } from 'antd'; import useClickOutside from 'hooks/useClickOutside'; import React, { useEffect, useRef, useState } from 'react'; import { connect, useDispatch, useSelector } from 'react-redux'; From 399efb0fb22d58ded18f69437b3f82d15a63dc33 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Wed, 6 Apr 2022 21:19:12 +0530 Subject: [PATCH 044/154] bug: trace filter bug are collapsed --- .../src/container/MetricsApplication/Tabs/Application.tsx | 4 ++-- .../src/container/MetricsApplication/TopEndpointsTable.tsx | 2 +- .../Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx | 1 - .../Trace/Filters/Panel/PanelBody/Duration/index.tsx | 1 - .../src/container/Trace/Filters/Panel/PanelHeading/index.tsx | 3 --- frontend/src/container/Trace/Search/index.tsx | 1 - frontend/src/container/Trace/TraceTable/index.tsx | 1 - frontend/src/store/actions/trace/getInitialSpansAggregate.ts | 1 - frontend/src/store/actions/trace/selectTraceFilter.ts | 1 - frontend/src/store/actions/trace/util.ts | 3 +-- 10 files changed, 4 insertions(+), 14 deletions(-) diff --git a/frontend/src/container/MetricsApplication/Tabs/Application.tsx b/frontend/src/container/MetricsApplication/Tabs/Application.tsx index 8cf4c2c3dd..8b8ae4f38f 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Application.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Application.tsx @@ -36,7 +36,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { history.replace( `${ ROUTES.TRACE - }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["ok","error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`, + }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=[]&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, ); }; @@ -88,7 +88,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { history.replace( `${ ROUTES.TRACE - }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`, + }?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=[]&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, ); }; diff --git a/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx b/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx index 1f1cbba643..71f19f3d7a 100644 --- a/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx +++ b/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx @@ -33,7 +33,7 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element { history.push( `${ ROUTES.TRACE - }?${urlParams.toString()}&selected={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&isSelectedFilterSkipped=true&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&isSelectedFilterSkipped=true`, + }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=[]&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, ); }; diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx index 3ac230f669..1c0c5fae75 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx @@ -130,7 +130,6 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { filterToFetchData, spansAggregate.currentPage, selectedTags, - updatedFilter, preIsFilterExclude, preUserSelectedMap, spansAggregate.order, diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx index 8453e5ca90..e6d44f3b61 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx @@ -126,7 +126,6 @@ function Duration(): JSX.Element { filterToFetchData, spansAggregate.currentPage, selectedTags, - preFilter, isFilterExclude, userSelectedFilter, spansAggregate.order, diff --git a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx index 8ab6e29d88..831bc245a5 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx @@ -110,7 +110,6 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { updatedFilterData, spansAggregate.currentPage, selectedTags, - updatedFilter, isFilterExclude, getPreUserSelected, spansAggregate.order, @@ -160,7 +159,6 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { preFilterToFetchTheData, spansAggregate.currentPage, selectedTags, - filter, isFilterExclude, userSelectedFilter, spansAggregate.order, @@ -210,7 +208,6 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { filterToFetchData, spansAggregate.currentPage, selectedTags, - getUpdatedFilter, postIsFilterExclude, preUserSelected, spansAggregate.order, diff --git a/frontend/src/container/Trace/Search/index.tsx b/frontend/src/container/Trace/Search/index.tsx index 1590fd74b2..446db559d0 100644 --- a/frontend/src/container/Trace/Search/index.tsx +++ b/frontend/src/container/Trace/Search/index.tsx @@ -105,7 +105,6 @@ function Search({ traces.filterToFetchData, traces.spansAggregate.currentPage, selectedTags, - traces.filter, traces.isFilterExclude, traces.userSelectedFilter, traces.spansAggregate.order, diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index 8115c8ee64..6efe341e59 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -151,7 +151,6 @@ function TraceTable(): JSX.Element { filterToFetchData, props.current, selectedTags, - filter, isFilterExclude, userSelectedFilter, spanOrder, diff --git a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts index a0725c9535..e723a84939 100644 --- a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts +++ b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts @@ -82,7 +82,6 @@ export const GetSpansAggregate = ( traces.filterToFetchData, props.current, traces.selectedTags, - traces.filter, traces.isFilterExclude, traces.userSelectedFilter, order, diff --git a/frontend/src/store/actions/trace/selectTraceFilter.ts b/frontend/src/store/actions/trace/selectTraceFilter.ts index 14e6b39fb4..23cf27d648 100644 --- a/frontend/src/store/actions/trace/selectTraceFilter.ts +++ b/frontend/src/store/actions/trace/selectTraceFilter.ts @@ -44,7 +44,6 @@ export const SelectedTraceFilter = (props: { traces.filterToFetchData, traces.spansAggregate.currentPage, traces.selectedTags, - traces.filter, traces.isFilterExclude, traces.userSelectedFilter, traces.spansAggregate.order, diff --git a/frontend/src/store/actions/trace/util.ts b/frontend/src/store/actions/trace/util.ts index 5355fc0660..1693f2693a 100644 --- a/frontend/src/store/actions/trace/util.ts +++ b/frontend/src/store/actions/trace/util.ts @@ -20,7 +20,6 @@ export const updateURL = ( filterToFetchData: TraceReducer['filterToFetchData'], spanAggregateCurrentPage: TraceReducer['spansAggregate']['currentPage'], selectedTags: TraceReducer['selectedTags'], - filter: TraceReducer['filter'], isFilterExclude: TraceReducer['isFilterExclude'], userSelectedFilter: TraceReducer['userSelectedFilter'], spanAggregateOrder: TraceReducer['spansAggregate']['order'], @@ -55,7 +54,7 @@ export const updateURL = ( filterToFetchData, )}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&selectedTags=${JSON.stringify( selectedTags, - )}&filter=${JSON.stringify(Object.fromEntries(filter))}&${preResult + )}&${preResult .map((e) => `${e.key}=${e.value}`) .join('&')}&isFilterExclude=${JSON.stringify( Object.fromEntries(isFilterExclude), From 5bdbe792f51d1bca421044c763af4f9fda316baa Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Wed, 6 Apr 2022 21:28:17 +0530 Subject: [PATCH 045/154] chore: selected filter is not removed when user close panel --- .../src/container/Trace/Filters/Panel/PanelHeading/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx index 831bc245a5..9d2b9b1da1 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx @@ -138,7 +138,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { ...filterToFetchData.filter((name) => name !== PanelName), ]; - preSelectedFilter.delete(PanelName); + // preSelectedFilter.delete(PanelName); dispatch({ type: UPDATE_ALL_FILTERS, From e864e33ad3cc584c9edac8bc995b897ddd87f5ac Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Wed, 6 Apr 2022 22:01:42 +0530 Subject: [PATCH 046/154] bug: value is updated on selection --- frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx index 378001bec4..d58675ffad 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx @@ -32,6 +32,7 @@ function TagValue(props: TagValueProps): JSX.Element { return ( { if (typeof value === 'string') { setLocalSelectedTags((tags) => [ From 647cabc4f462574e74c3d7cf1fe59baae42d5be4 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Thu, 7 Apr 2022 10:48:09 +0530 Subject: [PATCH 047/154] feat: migrated trace detail to use query (#963) * feat: migrated trace detail to use query * fix: remove unused imports * chore: useQuery config is updated Co-authored-by: Palash gupta --- frontend/package.json | 1 + frontend/src/index.tsx | 15 +++-- frontend/src/pages/TraceDetail/index.tsx | 19 +++--- frontend/yarn.lock | 77 +++++++++++++++++++++++- 4 files changed, 96 insertions(+), 16 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 88f09caa99..ed63737caf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,6 +68,7 @@ "react-graph-vis": "^1.0.5", "react-grid-layout": "^1.2.5", "react-i18next": "^11.16.1", + "react-query": "^3.34.19", "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "react-use": "^17.3.2", diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 86d258b26b..0abe9a6453 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -4,6 +4,7 @@ import './ReactI18'; import AppRoutes from 'AppRoutes'; import React from 'react'; import ReactDOM from 'react-dom'; +import { QueryClient, QueryClientProvider } from 'react-query'; import { Provider } from 'react-redux'; import reportWebVitals from 'reportWebVitals'; import store from 'store'; @@ -12,12 +13,16 @@ if (process.env.NODE_ENV === 'development') { reportWebVitals(console.log); } +const queryClient = new QueryClient(); + ReactDOM.render( - - - - - , + + + + + + + , document.querySelector('#root'), ); diff --git a/frontend/src/pages/TraceDetail/index.tsx b/frontend/src/pages/TraceDetail/index.tsx index 8b57b8668b..d8f6634888 100644 --- a/frontend/src/pages/TraceDetail/index.tsx +++ b/frontend/src/pages/TraceDetail/index.tsx @@ -2,27 +2,30 @@ import { Typography } from 'antd'; import getTraceItem from 'api/trace/getTraceItem'; import Spinner from 'components/Spinner'; import TraceDetailContainer from 'container/TraceDetail'; -import useFetch from 'hooks/useFetch'; import React from 'react'; +import { useQuery } from 'react-query'; import { useParams } from 'react-router-dom'; import { Props as TraceDetailProps } from 'types/api/trace/getTraceItem'; function TraceDetail(): JSX.Element { const { id } = useParams(); + const { data: traceDetailResponse, error, isLoading, isError } = useQuery( + `getTraceItem/${id}`, + () => getTraceItem({ id }), + { + cacheTime: 3000, + }, + ); - const traceDetailResponse = useFetch(getTraceItem, { - id, - }); - - if (traceDetailResponse.error) { + if (traceDetailResponse?.error || error || isError) { return ( - {traceDetailResponse.errorMessage || 'Something went wrong'} + {traceDetailResponse?.error || 'Something went wrong'} ); } - if (traceDetailResponse.loading || traceDetailResponse.payload === undefined) { + if (isLoading || !(traceDetailResponse && traceDetailResponse.payload)) { return ; } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5f063ef3f2..f7654074e0 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1070,7 +1070,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.17.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== @@ -3648,6 +3648,11 @@ bcrypt-pbkdf@^1.0.0: resolved "https://registry.yarnpkg.com/bezier-js/-/bezier-js-6.1.0.tgz#162b7bdbabe866e3a796285a89d71085140755ec" integrity sha512-oc8fkHqG0R+dQuNiXVbPMB0cc8iDqkLAjbA2gq26QmV8tZqW9GGI7iNEX1ioRWlZperQS7v5BX03+9FLVWZbSw== +big-integer@^1.6.16: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" @@ -3737,6 +3742,20 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -5399,7 +5418,7 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -detect-node@^2.0.4: +detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== @@ -7998,6 +8017,11 @@ js-cookie@^2.2.1: resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "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" @@ -8563,6 +8587,14 @@ marked@4.0.10: resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.10.tgz#423e295385cc0c3a70fa495e0df68b007b879423" integrity sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw== +match-sorter@^6.0.2: + version "6.3.1" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" + integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -8632,6 +8664,11 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -8789,6 +8826,13 @@ nano-css@^5.3.1: stacktrace-js "^2.0.2" stylis "^4.0.6" +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + nanoid@^2.0.3: version "2.1.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" @@ -9070,6 +9114,11 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + observable-fns@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/observable-fns/-/observable-fns-0.6.1.tgz#636eae4fdd1132e88c0faf38d33658cc79d87e37" @@ -10513,6 +10562,15 @@ react-motion@^0.5.2: prop-types "^15.5.8" raf "^3.1.0" +react-query@^3.34.19: + version "3.34.19" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.34.19.tgz#0ff049b6e0d2ed148e9abfdd346625d0e88dc229" + integrity sha512-JO0Ymi58WKmvnhgg6bGIrYIeKb64KsKaPWo8JcGnmK2jJxAs2XmMBzlP75ZepSU7CHzcsWtIIyhMrLbX3pb/3w== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + react-redux@^7.2.2: version "7.2.6" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa" @@ -10757,6 +10815,11 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -10904,7 +10967,7 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -12273,6 +12336,14 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" From 041a5249b3867b1c00b5be18c0b1944677043074 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Thu, 7 Apr 2022 13:11:27 +0530 Subject: [PATCH 048/154] feat: onClick is added in the row (#966) --- .../src/container/Trace/TraceTable/index.tsx | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index 6efe341e59..b68a180ad7 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -3,9 +3,9 @@ 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 { useDispatch, useSelector } from 'react-redux'; -import { Link } from 'react-router-dom'; import { Dispatch } from 'redux'; import { updateURL } from 'store/actions/trace/util'; import { AppState } from 'store/reducers'; @@ -40,30 +40,17 @@ function TraceTable(): JSX.Element { return `${ROUTES.TRACE}/${record.traceID}?spanId=${record.spanID}`; }; - const getValue = (value: string, record: TableType): JSX.Element => { - return ( - - {value} - - ); + const getValue = (value: string): JSX.Element => { + return {value}; }; const getHttpMethodOrStatus = ( value: TableType['httpMethod'], - record: TableType, ): JSX.Element => { if (value.length === 0) { - return ( - - - - - ); + return -; } - return ( - - {value} - - ); + return {value}; }; const columns: ColumnsType = [ @@ -72,13 +59,9 @@ function TraceTable(): JSX.Element { dataIndex: 'timestamp', key: 'timestamp', sorter: true, - render: (value: TableType['timestamp'], record): JSX.Element => { + render: (value: TableType['timestamp']): JSX.Element => { const day = dayjs(value); - return ( - - {day.format('YYYY/MM/DD HH:mm:ss')} - - ); + return {day.format('YYYY/MM/DD HH:mm:ss')}; }, }, { @@ -97,15 +80,13 @@ function TraceTable(): JSX.Element { title: 'Duration', dataIndex: 'durationNano', key: 'durationNano', - render: (value: TableType['durationNano'], record): JSX.Element => ( - - - {`${dayjs - .duration({ milliseconds: value / 1000000 }) - .asMilliseconds() - .toFixed(2)} ms`} - - + render: (value: TableType['durationNano']): JSX.Element => ( + + {`${dayjs + .duration({ milliseconds: value / 1000000 }) + .asMilliseconds() + .toFixed(2)} ms`} + ), }, { @@ -169,6 +150,13 @@ function TraceTable(): JSX.Element { style={{ cursor: 'pointer', }} + onRow={(record): React.HTMLAttributes => ({ + onClick: (event): void => { + event.preventDefault(); + event.stopPropagation(); + history.push(getLink(record)); + }, + })} pagination={{ current: spansAggregate.currentPage, pageSize: spansAggregate.pageSize, From dce9f36a8e7db0ab9eb5ae5d9ff337dfb03586ac Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Fri, 8 Apr 2022 02:12:54 +0530 Subject: [PATCH 049/154] chore: refetchOnWindowFocus is made false to global level --- frontend/src/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 0abe9a6453..99c7758c41 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -13,7 +13,13 @@ if (process.env.NODE_ENV === 'development') { reportWebVitals(console.log); } -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + }, + }, +}); ReactDOM.render( From 0cbe17a315418470d21e721d07b096f6b0f5d522 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Fri, 8 Apr 2022 02:15:50 +0530 Subject: [PATCH 050/154] chore(eslint): @typescript-eslint/no-unused-vars is made to error --- frontend/.eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index fb9d999579..54cc5e016c 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -101,6 +101,7 @@ module.exports = { }, }, ], + '@typescript-eslint/no-unused-vars': 'error', // eslint rules need to remove 'no-shadow': 'off', From 60288f7ba030c5d8736b5086a0dc328de5e25f6e Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Fri, 8 Apr 2022 13:49:33 +0530 Subject: [PATCH 051/154] chore(refactor): Signup app layout (#969) * feat: useFetch is upgraded to useFetchQueries * chore: en-gb and common.json is updated over public locale --- frontend/public/locales/en-GB/common.json | 3 + .../public/locales/en-GB/translation.json | 27 +++++++ frontend/public/locales/en/common.json | 3 + frontend/src/container/AppLayout/index.tsx | 80 +++++++++++++------ frontend/src/pages/SignUp/index.tsx | 53 +++++++----- 5 files changed, 125 insertions(+), 41 deletions(-) create mode 100644 frontend/public/locales/en-GB/common.json create mode 100644 frontend/public/locales/en-GB/translation.json create mode 100644 frontend/public/locales/en/common.json diff --git a/frontend/public/locales/en-GB/common.json b/frontend/public/locales/en-GB/common.json new file mode 100644 index 0000000000..c17e7e73dd --- /dev/null +++ b/frontend/public/locales/en-GB/common.json @@ -0,0 +1,3 @@ +{ + "something_went_wrong": "Something went wrong" +} diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json new file mode 100644 index 0000000000..23330080e6 --- /dev/null +++ b/frontend/public/locales/en-GB/translation.json @@ -0,0 +1,27 @@ +{ + "monitor_signup": "Monitor your applications. Find what is causing issues.", + "version": "Version", + "latest_version": "Latest version", + "current_version": "Current version", + "release_notes": "Release Notes", + "read_how_to_upgrade": "Read instructions on how to upgrade", + "latest_version_signoz": "You are running the latest version of SigNoz.", + "stale_version": "You are on an older version and may be loosing on the latest features we have shipped. We recommend to upgrade to the latest version", + "oops_something_went_wrong_version": "Oops.. facing issues with fetching updated version information", + "n_a": "N/A", + "routes": { + "general": "General", + "alert_channels": "Alert Channels" + }, + "settings": { + "total_retention_period": "Total Retention Period", + "move_to_s3": "Move to S3\n(should be lower than total retention period)", + "retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.", + "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", + "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", + "retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.", + "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", + "retention_confirmation": "Are you sure you want to change the retention period?", + "retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces." + } +} diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json new file mode 100644 index 0000000000..c17e7e73dd --- /dev/null +++ b/frontend/public/locales/en/common.json @@ -0,0 +1,3 @@ +{ + "something_went_wrong": "Something went wrong" +} diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 1fa47522c5..98910c60c5 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,13 +1,13 @@ import { notification } from 'antd'; -import getLatestVersion from 'api/user/getLatestVersion'; -import getVersion from 'api/user/getVersion'; +import getUserLatestVersion from 'api/user/getLatestVersion'; +import getUserVersion from 'api/user/getVersion'; import ROUTES from 'constants/routes'; import TopNav from 'container/Header'; import SideNav from 'container/SideNav'; -import useFetch from 'hooks/useFetch'; import history from 'lib/history'; import React, { ReactNode, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useQueries } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { Dispatch } from 'redux'; @@ -30,15 +30,28 @@ function AppLayout(props: AppLayoutProps): JSX.Element { const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname); - const { payload: versionPayload, loading, error: getVersionError } = useFetch( - getVersion, - ); + const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([ + { + queryFn: getUserVersion, + queryKey: 'getUserVersion', + enabled: isLoggedIn, + }, + { + queryFn: getUserLatestVersion, + queryKey: 'getUserLatestVersion', + enabled: isLoggedIn, + }, + ]); - const { - payload: latestVersionPayload, - loading: latestLoading, - error: latestError, - } = useFetch(getLatestVersion); + useEffect(() => { + if (getUserLatestVersionResponse.status === 'idle' && isLoggedIn) { + getUserLatestVersionResponse.refetch(); + } + + if (getUserVersionResponse.status === 'idle' && isLoggedIn) { + getUserVersionResponse.refetch(); + } + }, [getUserLatestVersionResponse, getUserVersionResponse, isLoggedIn]); const { children } = props; @@ -61,7 +74,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element { history.push(ROUTES.APPLICATION); } - if (!latestLoading && latestError && latestCurrentCounter.current === 0) { + if ( + getUserLatestVersionResponse.isFetched && + getUserLatestVersionResponse.isError && + latestCurrentCounter.current === 0 + ) { latestCurrentCounter.current = 1; dispatch({ @@ -75,7 +92,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element { }); } - if (!loading && getVersionError && latestVersionCounter.current === 0) { + if ( + getUserVersionResponse.isFetched && + getUserVersionResponse.isError && + latestVersionCounter.current === 0 + ) { latestVersionCounter.current = 1; dispatch({ @@ -89,34 +110,47 @@ function AppLayout(props: AppLayoutProps): JSX.Element { }); } - if (!latestLoading && versionPayload) { + if ( + getUserVersionResponse.isFetched && + getUserLatestVersionResponse.isSuccess && + getUserVersionResponse.data && + getUserVersionResponse.data.payload + ) { dispatch({ type: UPDATE_CURRENT_VERSION, payload: { - currentVersion: versionPayload.version, + currentVersion: getUserVersionResponse.data.payload.version, }, }); } - if (!loading && latestVersionPayload) { + if ( + getUserLatestVersionResponse.isFetched && + getUserLatestVersionResponse.isSuccess && + getUserLatestVersionResponse.data && + getUserLatestVersionResponse.data.payload + ) { dispatch({ type: UPDATE_LATEST_VERSION, payload: { - latestVersion: latestVersionPayload.name, + latestVersion: getUserLatestVersionResponse.data.payload.name, }, }); } }, [ dispatch, - loading, - latestLoading, - versionPayload, - latestVersionPayload, isLoggedIn, pathname, - getVersionError, - latestError, t, + getUserLatestVersionResponse.isLoading, + getUserLatestVersionResponse.isError, + getUserLatestVersionResponse.data, + getUserVersionResponse.isLoading, + getUserVersionResponse.isError, + getUserVersionResponse.data, + getUserLatestVersionResponse.isFetched, + getUserVersionResponse.isFetched, + getUserLatestVersionResponse.isSuccess, ]); return ( diff --git a/frontend/src/pages/SignUp/index.tsx b/frontend/src/pages/SignUp/index.tsx index 1bf16285a4..2518567adc 100644 --- a/frontend/src/pages/SignUp/index.tsx +++ b/frontend/src/pages/SignUp/index.tsx @@ -1,41 +1,58 @@ import { Typography } from 'antd'; -import getPreference from 'api/user/getPreference'; -import getVersion from 'api/user/getVersion'; +import getUserPreference from 'api/user/getPreference'; +import getUserVersion from 'api/user/getVersion'; import Spinner from 'components/Spinner'; -import useFetch from 'hooks/useFetch'; import React from 'react'; -import { PayloadProps as UserPrefPayload } from 'types/api/user/getUserPreference'; -import { PayloadProps as VersionPayload } from 'types/api/user/getVersion'; +import { useTranslation } from 'react-i18next'; +import { useQueries } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; import SignUpComponent from './SignUp'; function SignUp(): JSX.Element { - const versionResponse = useFetch(getVersion); + const { t } = useTranslation('common'); + const { isLoggedIn } = useSelector((state) => state.app); - const userPrefResponse = useFetch(getPreference); + const [versionResponse, userPrefResponse] = useQueries([ + { + queryFn: getUserVersion, + queryKey: 'getUserVersion', + enabled: !isLoggedIn, + }, + { + queryFn: getUserPreference, + queryKey: 'getUserPreference', + enabled: !isLoggedIn, + }, + ]); - if (versionResponse.error || userPrefResponse.error) { + if ( + versionResponse.status === 'error' || + userPrefResponse.status === 'error' + ) { return ( - {versionResponse.errorMessage || - userPrefResponse.errorMessage || - 'Somehthing went wrong'} + {versionResponse.data?.error || + userPrefResponse.data?.error || + t('something_went_wrong')} ); } if ( - versionResponse.loading || - versionResponse.payload === undefined || - userPrefResponse.loading || - userPrefResponse.payload === undefined + versionResponse.status === 'loading' || + userPrefResponse.status === 'loading' || + !(versionResponse.data && versionResponse.data.payload) || + !(userPrefResponse.data && userPrefResponse.data.payload) ) { - return ; + return ; } - const { version } = versionResponse.payload; + const { version } = versionResponse.data.payload; - const userpref = userPrefResponse.payload; + const userpref = userPrefResponse.data.payload; return ; } From d102c94670de8cea3179213748a63006f533d73c Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Fri, 8 Apr 2022 14:05:16 +0530 Subject: [PATCH 052/154] bug: unused import is removed and two unwanted eslint rule is removed (#968) --- frontend/.eslintrc.js | 2 -- .../Trace/Search/AllTags/Tag/TagValue.tsx | 2 -- .../src/container/Trace/TraceTable/index.tsx | 1 - .../src/container/TriggeredAlerts/index.tsx | 1 - frontend/src/wdyr.ts | 18 ++++++++++-------- frontend/tsconfig.json | 10 +++++++++- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index fb9d999579..b0234bbee3 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -105,8 +105,6 @@ module.exports = { // eslint rules need to remove 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'off', - 'global-require': 'off', - '@typescript-eslint/no-var-requires': 'off', 'import/no-cycle': 'off', 'prettier/prettier': [ diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx index d58675ffad..1d90703246 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx @@ -1,5 +1,4 @@ import { Select } from 'antd'; -import { DefaultOptionType } from 'antd/lib/select'; import getTagValue from 'api/trace/getTagValue'; import useFetch from 'hooks/useFetch'; import React from 'react'; @@ -9,7 +8,6 @@ import { PayloadProps, Props } from 'types/api/trace/getTagValue'; import { GlobalReducer } from 'types/reducer/globalTime'; import { TraceReducer } from 'types/reducer/trace'; -import { Value } from '.'; import { SelectComponent } from './styles'; function TagValue(props: TagValueProps): JSX.Element { diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index b68a180ad7..951c981ddd 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -25,7 +25,6 @@ function TraceTable(): JSX.Element { selectedTags, filterLoading, userSelectedFilter, - filter, isFilterExclude, filterToFetchData, } = useSelector((state) => state.traces); diff --git a/frontend/src/container/TriggeredAlerts/index.tsx b/frontend/src/container/TriggeredAlerts/index.tsx index 88ebb2e138..a3762762df 100644 --- a/frontend/src/container/TriggeredAlerts/index.tsx +++ b/frontend/src/container/TriggeredAlerts/index.tsx @@ -2,7 +2,6 @@ import getTriggeredApi from 'api/alerts/getTriggered'; import Spinner from 'components/Spinner'; import { State } from 'hooks/useFetch'; import React, { useCallback, useEffect, useState } from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; import { PayloadProps } from 'types/api/alerts/getTriggered'; import TriggerComponent from './TriggeredAlert'; diff --git a/frontend/src/wdyr.ts b/frontend/src/wdyr.ts index 8ba678a4d6..a69c889035 100644 --- a/frontend/src/wdyr.ts +++ b/frontend/src/wdyr.ts @@ -1,15 +1,17 @@ +/* eslint-disable global-require */ /// // ^ https://github.com/welldone-software/why-did-you-render/issues/161 import React from 'react'; if (process.env.NODE_ENV === 'development') { - const whyDidYouRender = require('@welldone-software/why-did-you-render'); - whyDidYouRender(React, { - trackAllPureComponents: false, - trackExtraHooks: [[require('react-redux/lib'), 'useSelector']], - include: [/^ConnectFunction/], - logOnDifferentValues: true, + import('@welldone-software/why-did-you-render').then((whyDidYouRender) => { + whyDidYouRender.default(React, { + trackAllPureComponents: true, + trackHooks: true, + // https://github.com/welldone-software/why-did-you-render/issues/85#issuecomment-596682587 + trackExtraHooks: [require('react-redux/lib'), 'useSelector'], + include: [/^ConnectFunction/], + logOnDifferentValues: true, + }); }); } - -export default ''; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index fcc3663f1b..4790ab4740 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -22,5 +22,13 @@ "plugins": [{ "name": "typescript-plugin-css-modules" }] }, "exclude": ["node_modules"], - "include": ["./src", "./babel.config.js", "./jest.config.ts"] + "include": [ + "./src", + "./babel.config.js", + "./jest.config.ts", + "./.eslintrc.js", + "./__mocks__", + "./conf/default.conf", + "./public" + ] } From a6c41f312df9d269ab766c572fc24013d22d426b Mon Sep 17 00:00:00 2001 From: Pranay Prateek Date: Fri, 8 Apr 2022 10:09:24 -0700 Subject: [PATCH 053/154] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0e7a7169b..26a1fb4ed4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,10 +18,10 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/frontend](https://git ### Contribute to Frontend with Docker installation of SigNoz - `git clone https://github.com/SigNoz/signoz.git && cd signoz` -- comment out frontend service section at `deploy/docker/clickhouse-setup/docker-compose.yaml#L59` +- comment out frontend service section at `deploy/docker/clickhouse-setup/docker-compose.yaml` - run `cd deploy` to move to deploy directory - Install signoz locally without the frontend - - Add below configuration to query-service section at `docker/clickhouse-setup/docker-compose.yaml#L36` + - Add below configuration to query-service section at `docker/clickhouse-setup/docker-compose.yaml` ```docker ports: @@ -55,9 +55,9 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/pkg/query-service](ht - git clone https://github.com/SigNoz/signoz.git - run `cd signoz` to move to signoz directory - run `sudo make dev-setup` to configure local setup to run query-service -- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml#L45` -- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml#L28` -- add below configuration to clickhouse section at `docker/clickhouse-setup/docker-compose.yaml#L6` +- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml` +- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml` +- add below configuration to clickhouse section at `docker/clickhouse-setup/docker-compose.yaml` ```docker expose: - 9000 From dc9508269d9f12d6de2a8d5308a6a203353bad59 Mon Sep 17 00:00:00 2001 From: Pranay Prateek Date: Fri, 8 Apr 2022 10:12:58 -0700 Subject: [PATCH 054/154] Update commentLinesForSetup.sh Updating frontend service line number --- .scripts/commentLinesForSetup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scripts/commentLinesForSetup.sh b/.scripts/commentLinesForSetup.sh index 7ea6b468ad..c0dfd40e9f 100644 --- a/.scripts/commentLinesForSetup.sh +++ b/.scripts/commentLinesForSetup.sh @@ -4,4 +4,4 @@ # Update the Line Numbers when deploy/docker/clickhouse-setup/docker-compose.yaml chnages. # Docs Ref.: https://github.com/SigNoz/signoz/blob/main/CONTRIBUTING.md#contribute-to-frontend-with-docker-installation-of-signoz -sed -i 38,70's/.*/# &/' .././deploy/docker/clickhouse-setup/docker-compose.yaml +sed -i 38,62's/.*/# &/' .././deploy/docker/clickhouse-setup/docker-compose.yaml From f6aece6349cd82d6531d414079b6de14ea0fbfbb Mon Sep 17 00:00:00 2001 From: Pranay Prateek Date: Fri, 8 Apr 2022 10:13:36 -0700 Subject: [PATCH 055/154] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26a1fb4ed4..6205d85884 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,10 +18,10 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/frontend](https://git ### Contribute to Frontend with Docker installation of SigNoz - `git clone https://github.com/SigNoz/signoz.git && cd signoz` -- comment out frontend service section at `deploy/docker/clickhouse-setup/docker-compose.yaml` +- comment out frontend service section at `deploy/docker/clickhouse-setup/docker-compose.yaml#L62` - run `cd deploy` to move to deploy directory - Install signoz locally without the frontend - - Add below configuration to query-service section at `docker/clickhouse-setup/docker-compose.yaml` + - Add below configuration to query-service section at `docker/clickhouse-setup/docker-compose.yaml#L38` ```docker ports: From d454482f433341e5770f0b46ab90cd7397ec9927 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Mon, 11 Apr 2022 17:17:31 +0530 Subject: [PATCH 056/154] wdyr is updated (#981) --- frontend/src/wdyr.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/wdyr.ts b/frontend/src/wdyr.ts index a69c889035..e64dcca566 100644 --- a/frontend/src/wdyr.ts +++ b/frontend/src/wdyr.ts @@ -1,17 +1,17 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable global-require */ /// // ^ https://github.com/welldone-software/why-did-you-render/issues/161 import React from 'react'; if (process.env.NODE_ENV === 'development') { - import('@welldone-software/why-did-you-render').then((whyDidYouRender) => { - whyDidYouRender.default(React, { - trackAllPureComponents: true, - trackHooks: true, - // https://github.com/welldone-software/why-did-you-render/issues/85#issuecomment-596682587 - trackExtraHooks: [require('react-redux/lib'), 'useSelector'], - include: [/^ConnectFunction/], - logOnDifferentValues: true, - }); + const whyDidYouRender = require('@welldone-software/why-did-you-render'); + whyDidYouRender(React, { + trackAllPureComponents: false, + trackExtraHooks: [[require('react-redux/lib'), 'useSelector']], + include: [/^ConnectFunction/], + logOnDifferentValues: true, }); } + +export default ''; From 61d01fa2d5710cf16080ca987667f7d61f04dbd9 Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Fri, 15 Apr 2022 11:32:19 +0530 Subject: [PATCH 057/154] feat: added action to verify that every pr has a linked issue --- .github/workflows/pr_verify_linked_issue.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/pr_verify_linked_issue.yml diff --git a/.github/workflows/pr_verify_linked_issue.yml b/.github/workflows/pr_verify_linked_issue.yml new file mode 100644 index 0000000000..adf2718a46 --- /dev/null +++ b/.github/workflows/pr_verify_linked_issue.yml @@ -0,0 +1,20 @@ +# This workflow will inspect a pull request to ensure there is a linked issue or a +# valid issue is mentioned in the body. If neither is present it fails the check and adds +# a comment alerting users of this missing requirement. +name: VerifyIssue + +on: + pull_request: + types: [edited, synchronize, opened, reopened] + check_run: + +jobs: + verify_linked_issue: + runs-on: ubuntu-latest + name: Ensure Pull Request has a linked issue. + steps: + - name: Verify Linked Issue + uses: hattan/verify-linked-issue-action@v1.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + From b2eec25f330e78c0f11bd6cd625f14e94b74f44a Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 18 Apr 2022 14:12:49 +0530 Subject: [PATCH 058/154] (bugfix): remove validation on post data id --- pkg/query-service/app/dashboards/model.go | 11 ++--------- pkg/query-service/app/http_handler.go | 9 ++------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/pkg/query-service/app/dashboards/model.go b/pkg/query-service/app/dashboards/model.go index 77b66046c0..0b4dc6847c 100644 --- a/pkg/query-service/app/dashboards/model.go +++ b/pkg/query-service/app/dashboards/model.go @@ -182,9 +182,7 @@ func GetDashboard(uuid string) (*Dashboard, *model.ApiError) { return &dashboard, nil } -func UpdateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError) { - - uuid := (*data)["uuid"].(string) +func UpdateDashboard(uuid string, data *map[string]interface{}) (*Dashboard, *model.ApiError) { map_data, err := json.Marshal(data) if err != nil { @@ -224,12 +222,7 @@ func (d *Dashboard) UpdateSlug() { func IsPostDataSane(data *map[string]interface{}) error { - val, ok := (*data)["uuid"] - if !ok || val == nil { - return fmt.Errorf("uuid not found in post data") - } - - val, ok = (*data)["title"] + val, ok := (*data)["title"] if !ok || val == nil { return fmt.Errorf("title not found in post data") } diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 771637935a..f3884127b5 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -13,10 +13,10 @@ import ( "github.com/prometheus/prometheus/promql" "go.signoz.io/query-service/app/dashboards" "go.signoz.io/query-service/dao/interfaces" + am "go.signoz.io/query-service/integrations/alertManager" "go.signoz.io/query-service/model" "go.signoz.io/query-service/telemetry" "go.signoz.io/query-service/version" - am "go.signoz.io/query-service/integrations/alertManager" "go.uber.org/zap" ) @@ -337,12 +337,7 @@ func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) { return } - if postData["uuid"] != uuid { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("uuid in request param and uuid in request body do not match")}, "Error reading request body") - return - } - - dashboard, apiError := dashboards.UpdateDashboard(&postData) + dashboard, apiError := dashboards.UpdateDashboard(uuid, &postData) if apiError != nil { aH.respondError(w, apiError, nil) From 93638d5615a6f79f9019b7b8dc89c8ad9241bba0 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Mon, 18 Apr 2022 15:24:51 +0530 Subject: [PATCH 059/154] Use fetch fix (#995) * feat: useFetch in tag value is removed and moved to use query * feat: useFetch in all channels is removed and moved to use query * feat: useFetch in edit rule is removed and moved to use query * feat: useFetch in general settings is removed and moved to use query * feat: useFetch in all alerts is changed into use query --- .../GeneralSettings/GeneralSettings.tsx | 323 +++++++++++++++++ .../src/container/GeneralSettings/index.tsx | 339 ++---------------- .../src/container/ListAlertRules/index.tsx | 20 +- .../Trace/Search/AllTags/Tag/TagValue.tsx | 26 +- frontend/src/pages/ChannelsEdit/index.tsx | 27 +- frontend/src/pages/EditRules/index.tsx | 23 +- 6 files changed, 405 insertions(+), 353 deletions(-) create mode 100644 frontend/src/container/GeneralSettings/GeneralSettings.tsx diff --git a/frontend/src/container/GeneralSettings/GeneralSettings.tsx b/frontend/src/container/GeneralSettings/GeneralSettings.tsx new file mode 100644 index 0000000000..768322d91d --- /dev/null +++ b/frontend/src/container/GeneralSettings/GeneralSettings.tsx @@ -0,0 +1,323 @@ +import { Button, Col, Modal, notification, Row, Typography } from 'antd'; +import setRetentionApi from 'api/settings/setRetention'; +import TextToolTip from 'components/TextToolTip'; +import find from 'lodash-es/find'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + IDiskType, + PayloadProps as GetDisksPayload, +} from 'types/api/disks/getDisks'; +import { PayloadProps as GetRetentionPayload } from 'types/api/settings/getRetention'; + +import Retention from './Retention'; +import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles'; + +type NumberOrNull = number | null; + +function GeneralSettings({ + ttlValuesPayload, + getAvailableDiskPayload, +}: GeneralSettingsProps): JSX.Element { + const { t } = useTranslation(); + const [modal, setModal] = useState(false); + const [postApiLoading, setPostApiLoading] = useState(false); + + const [availableDisks] = useState(getAvailableDiskPayload); + + const [currentTTLValues, setCurrentTTLValues] = useState(ttlValuesPayload); + + const [ + metricsTotalRetentionPeriod, + setMetricsTotalRetentionPeriod, + ] = useState(null); + const [ + metricsS3RetentionPeriod, + setMetricsS3RetentionPeriod, + ] = useState(null); + const [ + tracesTotalRetentionPeriod, + setTracesTotalRetentionPeriod, + ] = useState(null); + const [ + tracesS3RetentionPeriod, + setTracesS3RetentionPeriod, + ] = useState(null); + + useEffect(() => { + if (currentTTLValues) { + setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs); + setMetricsS3RetentionPeriod( + currentTTLValues.metrics_move_ttl_duration_hrs + ? currentTTLValues.metrics_move_ttl_duration_hrs + : null, + ); + setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs); + setTracesS3RetentionPeriod( + currentTTLValues.traces_move_ttl_duration_hrs + ? currentTTLValues.traces_move_ttl_duration_hrs + : null, + ); + } + }, [currentTTLValues]); + + const onModalToggleHandler = (): void => { + setModal((modal) => !modal); + }; + + const onClickSaveHandler = useCallback(() => { + onModalToggleHandler(); + }, []); + + const s3Enabled = useMemo( + () => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'), + [availableDisks], + ); + + const renderConfig = [ + { + name: 'Metrics', + retentionFields: [ + { + name: t('settings.total_retention_period'), + value: metricsTotalRetentionPeriod, + setValue: setMetricsTotalRetentionPeriod, + }, + { + name: t('settings.move_to_s3'), + value: metricsS3RetentionPeriod, + setValue: setMetricsS3RetentionPeriod, + hide: !s3Enabled, + }, + ], + }, + { + name: 'Traces', + retentionFields: [ + { + name: t('settings.total_retention_period'), + value: tracesTotalRetentionPeriod, + setValue: setTracesTotalRetentionPeriod, + }, + { + name: t('settings.move_to_s3'), + value: tracesS3RetentionPeriod, + setValue: setTracesS3RetentionPeriod, + hide: !s3Enabled, + }, + ], + }, + ].map((category): JSX.Element | null => { + if ( + Array.isArray(category.retentionFields) && + category.retentionFields.length > 0 + ) { + return ( + + {category.name} + + {category.retentionFields.map((retentionField) => ( + + ))} + + ); + } + return null; + }); + + // eslint-disable-next-line sonarjs/cognitive-complexity + const onOkHandler = async (): Promise => { + try { + setPostApiLoading(true); + const apiCalls = []; + + if ( + !( + currentTTLValues?.metrics_move_ttl_duration_hrs === + metricsS3RetentionPeriod && + currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod + ) + ) { + apiCalls.push(() => + setRetentionApi({ + type: 'metrics', + totalDuration: `${metricsTotalRetentionPeriod || -1}h`, + coldStorage: s3Enabled ? 's3' : null, + toColdDuration: `${metricsS3RetentionPeriod || -1}h`, + }), + ); + } else { + apiCalls.push(() => Promise.resolve(null)); + } + + if ( + !( + currentTTLValues?.traces_move_ttl_duration_hrs === + tracesS3RetentionPeriod && + currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod + ) + ) { + apiCalls.push(() => + setRetentionApi({ + type: 'traces', + totalDuration: `${tracesTotalRetentionPeriod || -1}h`, + coldStorage: s3Enabled ? 's3' : null, + toColdDuration: `${tracesS3RetentionPeriod || -1}h`, + }), + ); + } else { + apiCalls.push(() => Promise.resolve(null)); + } + const apiCallSequence = ['metrics', 'traces']; + const apiResponses = await Promise.all(apiCalls.map((api) => api())); + + apiResponses.forEach((apiResponse, idx) => { + const name = apiCallSequence[idx]; + if (apiResponse) { + if (apiResponse.statusCode === 200) { + notification.success({ + message: 'Success!', + placement: 'topRight', + + description: t('settings.retention_success_message', { name }), + }); + } else { + notification.error({ + message: 'Error', + description: t('settings.retention_error_message', { name }), + placement: 'topRight', + }); + } + } + }); + onModalToggleHandler(); + setPostApiLoading(false); + } catch (error) { + notification.error({ + message: 'Error', + description: t('settings.retention_failed_message'), + placement: 'topRight', + }); + } + // Updates the currentTTL Values in order to avoid pushing the same values. + setCurrentTTLValues({ + metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, + metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, + traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, + traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, + }); + + setModal(false); + }; + + // eslint-disable-next-line sonarjs/cognitive-complexity + const [isDisabled, errorText] = useMemo((): [boolean, string] => { + // Various methods to return dynamic error message text. + const messages = { + compareError: (name: string | number): string => + t('settings.retention_comparison_error', { name }), + nullValueError: (name: string | number): string => + t('settings.retention_null_value_error', { name }), + }; + + // Defaults to button not disabled and empty error message text. + let isDisabled = false; + let errorText = ''; + + if (s3Enabled) { + if ( + (metricsTotalRetentionPeriod || metricsS3RetentionPeriod) && + Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod) + ) { + isDisabled = true; + errorText = messages.compareError('metrics'); + } else if ( + (tracesTotalRetentionPeriod || tracesS3RetentionPeriod) && + Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod) + ) { + isDisabled = true; + errorText = messages.compareError('traces'); + } + } + + if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) { + isDisabled = true; + if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) { + errorText = messages.nullValueError('metrics and traces'); + } else if (!metricsTotalRetentionPeriod) { + errorText = messages.nullValueError('metrics'); + } else if (!tracesTotalRetentionPeriod) { + errorText = messages.nullValueError('traces'); + } + } + if ( + currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod && + currentTTLValues.metrics_move_ttl_duration_hrs === + metricsS3RetentionPeriod && + currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod && + currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod + ) { + isDisabled = true; + } + return [isDisabled, errorText]; + }, [ + currentTTLValues, + metricsS3RetentionPeriod, + metricsTotalRetentionPeriod, + s3Enabled, + t, + tracesS3RetentionPeriod, + tracesTotalRetentionPeriod, + ]); + + return ( + + {Element} + + + {errorText && {errorText}} + + + {renderConfig} + + + {t('settings.retention_confirmation_description')} + + + + + + + ); +} + +interface GeneralSettingsProps { + ttlValuesPayload: GetRetentionPayload; + getAvailableDiskPayload: GetDisksPayload; +} + +export default GeneralSettings; diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index 6a76282192..633c4822e6 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -1,331 +1,52 @@ -/* eslint-disable sonarjs/cognitive-complexity */ -import { Button, Col, Modal, notification, Row, Typography } from 'antd'; +import { Typography } from 'antd'; import getDisks from 'api/disks/getDisks'; import getRetentionPeriodApi from 'api/settings/getRetention'; -import setRetentionApi from 'api/settings/setRetention'; import Spinner from 'components/Spinner'; -import TextToolTip from 'components/TextToolTip'; -import useFetch from 'hooks/useFetch'; -import { find } from 'lodash-es'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { IDiskType } from 'types/api/disks/getDisks'; -import { PayloadProps } from 'types/api/settings/getRetention'; +import { useQueries } from 'react-query'; -import Retention from './Retention'; -import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles'; +import GeneralSettingsContainer from './GeneralSettings'; function GeneralSettings(): JSX.Element { - const { t } = useTranslation(); - const [notifications, Element] = notification.useNotification(); - const [modal, setModal] = useState(false); - const [postApiLoading, setPostApiLoading] = useState(false); - - const [availableDisks, setAvailableDisks] = useState(null); - - useEffect(() => { - getDisks().then((response) => setAvailableDisks(response.payload)); - }, []); - - const { payload, loading, error, errorMessage } = useFetch< - PayloadProps, - undefined - >(getRetentionPeriodApi, undefined); - - const [currentTTLValues, setCurrentTTLValues] = useState(payload); - - useEffect(() => { - setCurrentTTLValues(payload); - }, [payload]); - - const [metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod] = useState< - number | null - >(null); - const [metricsS3RetentionPeriod, setMetricsS3RetentionPeriod] = useState< - number | null - >(null); - const [tracesTotalRetentionPeriod, setTracesTotalRetentionPeriod] = useState< - number | null - >(null); - const [tracesS3RetentionPeriod, setTracesS3RetentionPeriod] = useState< - number | null - >(null); - - useEffect(() => { - if (currentTTLValues) { - setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs); - setMetricsS3RetentionPeriod( - currentTTLValues.metrics_move_ttl_duration_hrs - ? currentTTLValues.metrics_move_ttl_duration_hrs - : null, - ); - setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs); - setTracesS3RetentionPeriod( - currentTTLValues.traces_move_ttl_duration_hrs - ? currentTTLValues.traces_move_ttl_duration_hrs - : null, - ); - } - console.log({ changed: currentTTLValues }); - }, [currentTTLValues]); - - const onModalToggleHandler = (): void => { - setModal((modal) => !modal); - }; - - const onClickSaveHandler = useCallback(() => { - onModalToggleHandler(); - }, []); - - const s3Enabled = useMemo( - () => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'), - [availableDisks], - ); - - const renderConfig = [ + const { t } = useTranslation('common'); + const [getRetentionPeriodApiResponse, getDisksResponse] = useQueries([ { - name: 'Metrics', - retentionFields: [ - { - name: t('settings.total_retention_period'), - value: metricsTotalRetentionPeriod, - setValue: setMetricsTotalRetentionPeriod, - }, - { - name: t('settings.move_to_s3'), - value: metricsS3RetentionPeriod, - setValue: setMetricsS3RetentionPeriod, - hide: !s3Enabled, - }, - ], + queryFn: getRetentionPeriodApi, + queryKey: 'getRetentionPeriodApi', }, { - name: 'Traces', - retentionFields: [ - { - name: t('settings.total_retention_period'), - value: tracesTotalRetentionPeriod, - setValue: setTracesTotalRetentionPeriod, - }, - { - name: t('settings.move_to_s3'), - value: tracesS3RetentionPeriod, - setValue: setTracesS3RetentionPeriod, - hide: !s3Enabled, - }, - ], + queryFn: getDisks, + queryKey: 'getDisks', }, - ].map((category): JSX.Element | null => { - if ( - Array.isArray(category.retentionFields) && - category.retentionFields.length > 0 - ) { - return ( - - {category.name} - - {category.retentionFields.map((retentionField) => ( - - ))} - - ); - } - return null; - }); - - const onOkHandler = async (): Promise => { - try { - setPostApiLoading(true); - const apiCalls = []; - - if ( - !( - currentTTLValues?.metrics_move_ttl_duration_hrs === - metricsS3RetentionPeriod && - currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod - ) - ) { - apiCalls.push(() => - setRetentionApi({ - type: 'metrics', - totalDuration: `${metricsTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${metricsS3RetentionPeriod || -1}h`, - }), - ); - } else { - apiCalls.push(() => Promise.resolve(null)); - } - - if ( - !( - currentTTLValues?.traces_move_ttl_duration_hrs === - tracesS3RetentionPeriod && - currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod - ) - ) { - apiCalls.push(() => - setRetentionApi({ - type: 'traces', - totalDuration: `${tracesTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${tracesS3RetentionPeriod || -1}h`, - }), - ); - } else { - apiCalls.push(() => Promise.resolve(null)); - } - const apiCallSequence = ['metrics', 'traces']; - const apiResponses = await Promise.all(apiCalls.map((api) => api())); - - apiResponses.forEach((apiResponse, idx) => { - const name = apiCallSequence[idx]; - if (apiResponse) { - if (apiResponse.statusCode === 200) { - notifications.success({ - message: 'Success!', - placement: 'topRight', - - description: t('settings.retention_success_message', { name }), - }); - } else { - notifications.error({ - message: 'Error', - description: t('settings.retention_error_message', { name }), - placement: 'topRight', - }); - } - } - }); - onModalToggleHandler(); - setPostApiLoading(false); - } catch (error) { - notifications.error({ - message: 'Error', - description: t('settings.retention_failed_message'), - placement: 'topRight', - }); - } - // Updates the currentTTL Values in order to avoid pushing the same values. - setCurrentTTLValues({ - metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, - metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, - traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, - traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, - }); - - setModal(false); - }; - - const [isDisabled, errorText] = useMemo((): [boolean, string] => { - // Various methods to return dynamic error message text. - const messages = { - compareError: (name: string | number): string => - t('settings.retention_comparison_error', { name }), - nullValueError: (name: string | number): string => - t('settings.retention_null_value_error', { name }), - }; - - // Defaults to button not disabled and empty error message text. - let isDisabled = false; - let errorText = ''; - - if (s3Enabled) { - if ( - (metricsTotalRetentionPeriod || metricsS3RetentionPeriod) && - Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod) - ) { - isDisabled = true; - errorText = messages.compareError('metrics'); - } else if ( - (tracesTotalRetentionPeriod || tracesS3RetentionPeriod) && - Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod) - ) { - isDisabled = true; - errorText = messages.compareError('traces'); - } - } - - if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) { - isDisabled = true; - if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) { - errorText = messages.nullValueError('metrics and traces'); - } else if (!metricsTotalRetentionPeriod) { - errorText = messages.nullValueError('metrics'); - } else if (!tracesTotalRetentionPeriod) { - errorText = messages.nullValueError('traces'); - } - } - if ( - currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod && - currentTTLValues.metrics_move_ttl_duration_hrs === - metricsS3RetentionPeriod && - currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod && - currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod - ) { - isDisabled = true; - } - return [isDisabled, errorText]; - }, [ - currentTTLValues, - metricsS3RetentionPeriod, - metricsTotalRetentionPeriod, - s3Enabled, - t, - tracesS3RetentionPeriod, - tracesTotalRetentionPeriod, ]); - if (error) { - return {errorMessage}; + if (getRetentionPeriodApiResponse.isError || getDisksResponse.isError) { + return ( + + {getRetentionPeriodApiResponse.data?.error || + getDisksResponse.data?.error || + t('something_went_wrong')} + + ); } - if (loading || currentTTLValues === undefined) { + if ( + getRetentionPeriodApiResponse.isLoading || + getDisksResponse.isLoading || + !getDisksResponse.data?.payload || + !getRetentionPeriodApiResponse.data?.payload + ) { return ; } return ( - - {Element} - - - {errorText && {errorText}} - - - {renderConfig} - - - {t('settings.retention_confirmation_description')} - - - - - - + ); } diff --git a/frontend/src/container/ListAlertRules/index.tsx b/frontend/src/container/ListAlertRules/index.tsx index ce44e871e9..8fd131736c 100644 --- a/frontend/src/container/ListAlertRules/index.tsx +++ b/frontend/src/container/ListAlertRules/index.tsx @@ -1,29 +1,29 @@ import getAll from 'api/alerts/getAll'; import Spinner from 'components/Spinner'; -import useFetch from 'hooks/useFetch'; import React from 'react'; -import { PayloadProps } from 'types/api/alerts/getAll'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; import ListAlert from './ListAlert'; function ListAlertRules(): JSX.Element { - const { loading, payload, error, errorMessage } = useFetch< - PayloadProps, - undefined - >(getAll); + const { t } = useTranslation('common'); + const { data, isError, isLoading } = useQuery('allAlerts', { + queryFn: getAll, + }); - if (error) { - return
{errorMessage}
; + if (isError) { + return
{data?.error || t('something_went_wrong')}
; } - if (loading || payload === undefined) { + if (isLoading || !data?.payload) { return ; } return ( ); diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx index 1d90703246..756bb54225 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx @@ -1,10 +1,9 @@ import { Select } from 'antd'; import getTagValue from 'api/trace/getTagValue'; -import useFetch from 'hooks/useFetch'; import React from 'react'; +import { useQuery } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import { PayloadProps, Props } from 'types/api/trace/getTagValue'; import { GlobalReducer } from 'types/reducer/globalTime'; import { TraceReducer } from 'types/reducer/trace'; @@ -22,11 +21,17 @@ function TagValue(props: TagValueProps): JSX.Element { (state) => state.globalTime, ); - const valueSuggestion = useFetch(getTagValue, { - end: globalReducer.maxTime, - start: globalReducer.minTime, - tagKey, - }); + const { isLoading, data } = useQuery( + ['tagKey', globalReducer.minTime, globalReducer.maxTime, tagKey], + { + queryFn: () => + getTagValue({ + end: globalReducer.maxTime, + start: globalReducer.minTime, + tagKey, + }), + }, + ); return ( - {valueSuggestion.payload && - valueSuggestion.payload.map((suggestion) => ( + {data && + data.payload && + data.payload.map((suggestion) => ( {suggestion.tagValues} diff --git a/frontend/src/pages/ChannelsEdit/index.tsx b/frontend/src/pages/ChannelsEdit/index.tsx index 4048eda81c..dc2804da53 100644 --- a/frontend/src/pages/ChannelsEdit/index.tsx +++ b/frontend/src/pages/ChannelsEdit/index.tsx @@ -8,32 +8,33 @@ import { WebhookType, } from 'container/CreateAlertChannels/config'; import EditAlertChannels from 'container/EditAlertChannels'; -import useFetch from 'hooks/useFetch'; import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; import { useParams } from 'react-router-dom'; -import { PayloadProps, Props } from 'types/api/channels/get'; function ChannelsEdit(): JSX.Element { const { id } = useParams(); + const { t } = useTranslation(); - const { errorMessage, payload, error, loading } = useFetch< - PayloadProps, - Props - >(get, { - id, + const { isLoading, isError, data } = useQuery(['getChannel', id], { + queryFn: () => + get({ + id, + }), }); - if (error) { - return {errorMessage}; + if (isError) { + return {data?.error || t('something_went_wrong')}; } - if (loading || payload === undefined) { + if (isLoading || !data?.payload) { return ; } - const { data } = payload; + const { data: ChannelData } = data.payload; - const value = JSON.parse(data); + const value = JSON.parse(ChannelData); let type = ''; let channel: SlackChannel & WebhookChannel = { name: '' }; @@ -57,7 +58,7 @@ function ChannelsEdit(): JSX.Element { } type = WebhookType; } - console.log('channel:', channel); + return ( (); + const { t } = useTranslation('common'); - const { loading, error, payload, errorMessage } = useFetch< - PayloadProps, - Props - >(get, { - id: parseInt(ruleId, 10), + const { isLoading, data, isError } = useQuery(['ruleId', ruleId], { + queryFn: () => + get({ + id: parseInt(ruleId, 10), + }), }); - if (error) { - return
{errorMessage}
; + if (isError) { + return
{data?.error || t('something_went_wrong')}
; } - if (loading || payload === undefined) { + if (isLoading || !data?.payload) { return ; } - return ; + return ; } interface EditRulesParam { From 08bbb0259d6d15afcb7b6d63f1f42643eb8ef289 Mon Sep 17 00:00:00 2001 From: Palash gupta Date: Tue, 19 Apr 2022 00:21:30 +0530 Subject: [PATCH 060/154] feat: httpCode and httpStatus is updated to code and method --- frontend/src/container/Trace/TraceTable/index.tsx | 10 +++++----- frontend/src/types/reducer/trace.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index 951c981ddd..18d73bf5f0 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -44,7 +44,7 @@ function TraceTable(): JSX.Element { }; const getHttpMethodOrStatus = ( - value: TableType['httpMethod'], + value: TableType['statusCode'], ): JSX.Element => { if (value.length === 0) { return -; @@ -90,14 +90,14 @@ function TraceTable(): JSX.Element { }, { title: 'Method', - dataIndex: 'httpMethod', - key: 'httpMethod', + dataIndex: 'method', + key: 'method', render: getHttpMethodOrStatus, }, { title: 'Status Code', - dataIndex: 'httpCode', - key: 'httpCode', + dataIndex: 'statusCode', + key: 'statusCode', render: getHttpMethodOrStatus, }, ]; diff --git a/frontend/src/types/reducer/trace.ts b/frontend/src/types/reducer/trace.ts index 339fd63483..5989c58498 100644 --- a/frontend/src/types/reducer/trace.ts +++ b/frontend/src/types/reducer/trace.ts @@ -40,8 +40,8 @@ interface SpansAggregateData { serviceName: string; operation: string; durationNano: number; - httpCode: string; - httpMethod: string; + statusCode: string; + method: string; } export interface Tags { From 3c2173de9e5f30a99ddcaca8939a8a52a7559f7e Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Tue, 19 Apr 2022 10:57:56 +0530 Subject: [PATCH 061/154] feat: new dashboard widget's option selection (#982) * feat: new dashboard widget's option selection * fix: overflowing legend * feat: delete menu item is of type danger * feat: added keyboard events onFocus and onBlur --- .../GridGraphLayout/Graph/Bar/index.tsx | 39 ------- .../GridGraphLayout/Graph/Bar/styles.ts | 15 --- .../container/GridGraphLayout/Graph/index.tsx | 38 ++++-- .../GridGraphLayout/WidgetHeader/index.tsx | 109 ++++++++++++++++++ .../GridGraphLayout/WidgetHeader/styles.ts | 30 +++++ 5 files changed, 167 insertions(+), 64 deletions(-) delete mode 100644 frontend/src/container/GridGraphLayout/Graph/Bar/index.tsx delete mode 100644 frontend/src/container/GridGraphLayout/Graph/Bar/styles.ts create mode 100644 frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx create mode 100644 frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts diff --git a/frontend/src/container/GridGraphLayout/Graph/Bar/index.tsx b/frontend/src/container/GridGraphLayout/Graph/Bar/index.tsx deleted file mode 100644 index 7214d4839e..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/Bar/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { - DeleteOutlined, - EditFilled, - FullscreenOutlined, -} from '@ant-design/icons'; -import history from 'lib/history'; -import React from 'react'; -import { Widgets } from 'types/api/dashboard/getAll'; - -import { Container } from './styles'; - -function Bar({ - widget, - onViewFullScreenHandler, - onDeleteHandler, -}: BarProps): JSX.Element { - const onEditHandler = (): void => { - const widgetId = widget.id; - history.push( - `${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`, - ); - }; - - return ( - - - - - - ); -} - -interface BarProps { - widget: Widgets; - onViewFullScreenHandler: () => void; - onDeleteHandler: () => void; -} - -export default Bar; diff --git a/frontend/src/container/GridGraphLayout/Graph/Bar/styles.ts b/frontend/src/container/GridGraphLayout/Graph/Bar/styles.ts deleted file mode 100644 index ca8073a672..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/Bar/styles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import styled from 'styled-components'; - -export const Container = styled.div` - height: 15%; - align-items: center; - justify-content: flex-end; - display: flex; - gap: 1rem; - padding-right: 1rem; - padding-left: 1rem; - padding-top: 0.5rem; - position: absolute; - top: 0; - right: 0; -`; diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index 0447cfca94..eb1ed0e2d6 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -20,7 +20,7 @@ import AppActions from 'types/actions'; import { GlobalTime } from 'types/actions/globalTime'; import { Widgets } from 'types/api/dashboard/getAll'; -import Bar from './Bar'; +import WidgetHeader from '../WidgetHeader'; import FullView from './FullView'; import { ErrorContainer, FullViewContainer, Modal } from './styles'; @@ -37,6 +37,7 @@ function GridCardGraph({ error: false, payload: undefined, }); + const [hovered, setHovered] = useState(false); const [modal, setModal] = useState(false); const { minTime, maxTime } = useSelector( (state) => state.globalTime, @@ -171,10 +172,12 @@ function GridCardGraph({ return ( <> {getModals()} - onToggleModal(setModal)} + onToggleModal(setDeletModal)} + onView={(): void => onToggleModal(setModal)} + onDelete={(): void => onToggleModal(setDeletModal)} /> {state.errorMessage} @@ -187,11 +190,26 @@ function GridCardGraph({ } return ( - <> - onToggleModal(setModal)} + { + setHovered(true); + }} + onFocus={(): void => { + setHovered(true); + }} + onMouseOut={(): void => { + setHovered(false); + }} + onBlur={(): void => { + setHovered(false); + }} + > + onToggleModal(setDeletModal)} + onView={(): void => onToggleModal(setModal)} + onDelete={(): void => onToggleModal(setDeletModal)} /> {getModals()} @@ -202,12 +220,12 @@ function GridCardGraph({ data: state.payload, isStacked: widget.isStacked, opacity: widget.opacity, - title: widget.title, + title: ' ', // empty title to accommodate absolutely positioned widget header name, yAxisUnit, }} /> - + ); } diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx new file mode 100644 index 0000000000..ce9478d264 --- /dev/null +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx @@ -0,0 +1,109 @@ +import { + DeleteOutlined, + DownOutlined, + EditFilled, + FullscreenOutlined, +} from '@ant-design/icons'; +import { Dropdown, Menu, Typography } from 'antd'; +import history from 'lib/history'; +import React, { useState } from 'react'; +import { Widgets } from 'types/api/dashboard/getAll'; + +import { + ArrowContainer, + HeaderContainer, + HeaderContentContainer, + MenuItemContainer, +} from './styles'; + +type TWidgetOptions = 'view' | 'edit' | 'delete' | string; +interface IWidgetHeaderProps { + title: string; + widget: Widgets; + onView: VoidFunction; + onDelete: VoidFunction; + parentHover: boolean; +} +function WidgetHeader({ + title, + widget, + onView, + onDelete, + parentHover, +}: IWidgetHeaderProps): JSX.Element { + const [localHover, setLocalHover] = useState(false); + + const onEditHandler = (): void => { + const widgetId = widget.id; + history.push( + `${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`, + ); + }; + + const keyMethodMapping: { + [K in TWidgetOptions]: { key: TWidgetOptions; method: VoidFunction }; + } = { + view: { + key: 'view', + method: onView, + }, + edit: { + key: 'edit', + method: onEditHandler, + }, + delete: { + key: 'delete', + method: onDelete, + }, + }; + const onMenuItemSelectHandler = ({ key }: { key: TWidgetOptions }): void => { + keyMethodMapping[key]?.method(); + }; + + const menu = ( + + + + View + + + + + Edit + + + + + + Delete + + + + ); + + return ( + + setLocalHover(true)} + onMouseOut={(): void => setLocalHover(false)} + hover={localHover} + > + e.preventDefault()}> + + {title} + + + + + + + + ); +} + +export default WidgetHeader; diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts b/frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts new file mode 100644 index 0000000000..9600a6bcb4 --- /dev/null +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts @@ -0,0 +1,30 @@ +import { grey } from '@ant-design/colors'; +import styled from 'styled-components'; + +export const MenuItemContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +export const HeaderContainer = styled.div<{ hover: boolean }>` + width: 100%; + text-align: center; + background: ${({ hover }): string => (hover ? `${grey[0]}66` : 'inherit')}; + padding: 0.25rem 0; + font-size: 0.8rem; + cursor: all-scroll; + position: absolute; +`; + +export const HeaderContentContainer = styled.span` + cursor: pointer; + position: relative; + text-align: center; +`; + +export const ArrowContainer = styled.span<{ hover: boolean }>` + visibility: ${({ hover }): string => (hover ? 'visible' : 'hidden')}; + position: absolute; + right: -1rem; +`; From 508c6ced807e4be406035497af0db13958a3ab51 Mon Sep 17 00:00:00 2001 From: Amol Umbark Date: Fri, 22 Apr 2022 12:11:19 +0530 Subject: [PATCH 062/154] (feature): API - Implement receiver/channel test functionality (#993) * (feature): Added test receiver/channel functionality --- pkg/query-service/app/http_handler.go | 31 ++++++++++ .../integrations/alertManager/manager.go | 58 ++++++++++++++----- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index f3884127b5..fd4ecf4360 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -39,6 +39,7 @@ type APIHandler struct { basePath string apiPrefix string reader *Reader + alertManager am.Manager relationalDB *interfaces.ModelDao ready func(http.HandlerFunc) http.HandlerFunc } @@ -46,9 +47,11 @@ type APIHandler struct { // NewAPIHandler returns an APIHandler func NewAPIHandler(reader *Reader, relationalDB *interfaces.ModelDao) (*APIHandler, error) { + alertManager := am.New("") aH := &APIHandler{ reader: reader, relationalDB: relationalDB, + alertManager: alertManager, } aH.ready = aH.testReady @@ -172,6 +175,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/channels/{id}", aH.editChannel).Methods(http.MethodPut) router.HandleFunc("/api/v1/channels/{id}", aH.deleteChannel).Methods(http.MethodDelete) router.HandleFunc("/api/v1/channels", aH.createChannel).Methods(http.MethodPost) + router.HandleFunc("/api/v1/testChannel", aH.testChannel).Methods(http.MethodPost) router.HandleFunc("/api/v1/rules", aH.listRulesFromProm).Methods(http.MethodGet) router.HandleFunc("/api/v1/rules/{id}", aH.getRule).Methods(http.MethodGet) router.HandleFunc("/api/v1/rules", aH.createRule).Methods(http.MethodPost) @@ -451,6 +455,33 @@ func (aH *APIHandler) listChannels(w http.ResponseWriter, r *http.Request) { aH.respond(w, channels) } +// testChannels sends test alert to all registered channels +func (aH *APIHandler) testChannel(w http.ResponseWriter, r *http.Request) { + + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + zap.S().Errorf("Error in getting req body of testChannel API\n", err) + aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + receiver := &am.Receiver{} + if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer + zap.S().Errorf("Error in parsing req body of testChannel API\n", err) + aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + // send alert + apiErrorObj := aH.alertManager.TestReceiver(receiver) + if apiErrorObj != nil { + aH.respondError(w, apiErrorObj, nil) + return + } + aH.respond(w, "test alert sent") +} + func (aH *APIHandler) editChannel(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] diff --git a/pkg/query-service/integrations/alertManager/manager.go b/pkg/query-service/integrations/alertManager/manager.go index f0e8b024d1..47dc96f366 100644 --- a/pkg/query-service/integrations/alertManager/manager.go +++ b/pkg/query-service/integrations/alertManager/manager.go @@ -2,13 +2,14 @@ package alertManager // Wrapper to connect and process alert manager functions import ( - "fmt" - "encoding/json" "bytes" + "encoding/json" + "fmt" "net/http" - "go.uber.org/zap" + "go.signoz.io/query-service/constants" "go.signoz.io/query-service/model" + "go.uber.org/zap" ) const contentType = "application/json" @@ -17,16 +18,17 @@ type Manager interface { AddRoute(receiver *Receiver) *model.ApiError EditRoute(receiver *Receiver) *model.ApiError DeleteRoute(name string) *model.ApiError + TestReceiver(receiver *Receiver) *model.ApiError } -func New(url string) Manager{ - - if url == ""{ +func New(url string) Manager { + + if url == "" { url = constants.GetAlertManagerApiPrefix() } - return &manager { - url: url, + return &manager{ + url: url, } } @@ -34,11 +36,10 @@ type manager struct { url string } - func prepareAmChannelApiURL() string { basePath := constants.GetAlertManagerApiPrefix() AmChannelApiPath := constants.AmChannelApiPath - + if len(AmChannelApiPath) > 0 && rune(AmChannelApiPath[0]) == rune('/') { AmChannelApiPath = AmChannelApiPath[1:] } @@ -46,13 +47,18 @@ func prepareAmChannelApiURL() string { return fmt.Sprintf("%s%s", basePath, AmChannelApiPath) } -func (m *manager) AddRoute(receiver *Receiver) (*model.ApiError) { - +func prepareTestApiURL() string { + basePath := constants.GetAlertManagerApiPrefix() + return fmt.Sprintf("%s%s", basePath, "v1/testReceiver") +} + +func (m *manager) AddRoute(receiver *Receiver) *model.ApiError { + receiverString, _ := json.Marshal(receiver) amURL := prepareAmChannelApiURL() response, err := http.Post(amURL, contentType, bytes.NewBuffer(receiverString)) - + if err != nil { zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(POST %s)\n", amURL), err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -81,7 +87,7 @@ func (m *manager) EditRoute(receiver *Receiver) *model.ApiError { client := &http.Client{} response, err := client.Do(req) - + if err != nil { zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(PUT %s)\n", amURL), err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -125,5 +131,29 @@ func (m *manager) DeleteRoute(name string) *model.ApiError { return nil } +func (m *manager) TestReceiver(receiver *Receiver) *model.ApiError { + receiverBytes, _ := json.Marshal(receiver) + amTestURL := prepareTestApiURL() + response, err := http.Post(amTestURL, contentType, bytes.NewBuffer(receiverBytes)) + + if err != nil { + zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(POST %s)\n", amTestURL), err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if response.StatusCode > 201 && response.StatusCode < 400 { + err := fmt.Errorf(fmt.Sprintf("Invalid parameters in test alert api for alertmanager(POST %s)\n", amTestURL), response.Status) + zap.S().Error(err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if response.StatusCode > 400 { + err := fmt.Errorf(fmt.Sprintf("Received Server Error response for API call to alertmanager(POST %s)\n", amTestURL), response.Status) + zap.S().Error(err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + return nil +} From 2b5b79e34a2beda439feac7d2ad234187db50a48 Mon Sep 17 00:00:00 2001 From: Amol Umbark Date: Fri, 22 Apr 2022 16:56:18 +0530 Subject: [PATCH 063/154] (feature): UI for Test alert channels (#994) * (feature): Implemented test channel function for webhook and slack --- frontend/public/locales/en-GB/channels.json | 30 ++++ frontend/public/locales/en/channels.json | 30 ++++ frontend/src/api/channels/testSlack.ts | 35 ++++ frontend/src/api/channels/testWebhook.ts | 51 ++++++ .../container/CreateAlertChannels/index.tsx | 155 ++++++++++++------ .../src/container/EditAlertChannels/index.tsx | 126 +++++++++++--- .../FormAlertChannels/Settings/Slack.tsx | 15 +- .../FormAlertChannels/Settings/Webhook.tsx | 11 +- .../src/container/FormAlertChannels/index.tsx | 24 ++- 9 files changed, 389 insertions(+), 88 deletions(-) create mode 100644 frontend/public/locales/en-GB/channels.json create mode 100644 frontend/public/locales/en/channels.json create mode 100644 frontend/src/api/channels/testSlack.ts create mode 100644 frontend/src/api/channels/testWebhook.ts diff --git a/frontend/public/locales/en-GB/channels.json b/frontend/public/locales/en-GB/channels.json new file mode 100644 index 0000000000..1378ba73b8 --- /dev/null +++ b/frontend/public/locales/en-GB/channels.json @@ -0,0 +1,30 @@ +{ + "page_title_create": "New Notification Channels", + "page_title_edit": "Edit Notification Channels", + "button_save_channel": "Save", + "button_test_channel": "Test", + "button_return": "Back", + "field_channel_name": "Name", + "field_channel_type": "Type", + "field_webhook_url": "Webhook URL", + "field_slack_recipient": "Recipient", + "field_slack_title": "Title", + "field_slack_description": "Description", + "field_webhook_username": "User Name (optional)", + "field_webhook_password": "Password (optional)", + "placeholder_slack_description": "Description", + "help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.", + "help_webhook_password": "Specify a password or bearer token", + "channel_creation_done": "Successfully created the channel", + "channel_creation_failed": "An unexpected error occurred while creating this channel", + "channel_edit_done": "Channels Edited Successfully", + "channel_edit_failed": "An unexpected error occurred while updating this channel", + "selected_channel_invalid": "Channel type selected is invalid", + "username_no_password": "A Password must be provided with user name", + "test_unsupported": "Sorry, this channel type does not support test yet", + "channel_test_done": "An alert has been sent to this channel", + "channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly", + "channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again", + "webhook_url_required": "Webhook URL is mandatory", + "slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)" +} \ No newline at end of file diff --git a/frontend/public/locales/en/channels.json b/frontend/public/locales/en/channels.json new file mode 100644 index 0000000000..1378ba73b8 --- /dev/null +++ b/frontend/public/locales/en/channels.json @@ -0,0 +1,30 @@ +{ + "page_title_create": "New Notification Channels", + "page_title_edit": "Edit Notification Channels", + "button_save_channel": "Save", + "button_test_channel": "Test", + "button_return": "Back", + "field_channel_name": "Name", + "field_channel_type": "Type", + "field_webhook_url": "Webhook URL", + "field_slack_recipient": "Recipient", + "field_slack_title": "Title", + "field_slack_description": "Description", + "field_webhook_username": "User Name (optional)", + "field_webhook_password": "Password (optional)", + "placeholder_slack_description": "Description", + "help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.", + "help_webhook_password": "Specify a password or bearer token", + "channel_creation_done": "Successfully created the channel", + "channel_creation_failed": "An unexpected error occurred while creating this channel", + "channel_edit_done": "Channels Edited Successfully", + "channel_edit_failed": "An unexpected error occurred while updating this channel", + "selected_channel_invalid": "Channel type selected is invalid", + "username_no_password": "A Password must be provided with user name", + "test_unsupported": "Sorry, this channel type does not support test yet", + "channel_test_done": "An alert has been sent to this channel", + "channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly", + "channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again", + "webhook_url_required": "Webhook URL is mandatory", + "slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)" +} \ No newline at end of file diff --git a/frontend/src/api/channels/testSlack.ts b/frontend/src/api/channels/testSlack.ts new file mode 100644 index 0000000000..a2b4b1f40a --- /dev/null +++ b/frontend/src/api/channels/testSlack.ts @@ -0,0 +1,35 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/channels/createSlack'; + +const testSlack = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/testChannel', { + name: props.name, + slack_configs: [ + { + send_resolved: true, + api_url: props.api_url, + channel: props.channel, + title: props.title, + text: props.text, + }, + ], + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default testSlack; diff --git a/frontend/src/api/channels/testWebhook.ts b/frontend/src/api/channels/testWebhook.ts new file mode 100644 index 0000000000..4b915e9a3a --- /dev/null +++ b/frontend/src/api/channels/testWebhook.ts @@ -0,0 +1,51 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/channels/createWebhook'; + +const testWebhook = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + let httpConfig = {}; + + if (props.username !== '' && props.password !== '') { + httpConfig = { + basic_auth: { + username: props.username, + password: props.password, + }, + }; + } else if (props.username === '' && props.password !== '') { + httpConfig = { + authorization: { + type: 'bearer', + credentials: props.password, + }, + }; + } + + const response = await axios.post('/testChannel', { + name: props.name, + webhook_configs: [ + { + send_resolved: true, + url: props.api_url, + http_config: httpConfig, + }, + ], + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default testWebhook; diff --git a/frontend/src/container/CreateAlertChannels/index.tsx b/frontend/src/container/CreateAlertChannels/index.tsx index 02cd7b274a..dc3eee86a6 100644 --- a/frontend/src/container/CreateAlertChannels/index.tsx +++ b/frontend/src/container/CreateAlertChannels/index.tsx @@ -1,10 +1,13 @@ import { Form, notification } from 'antd'; import createSlackApi from 'api/channels/createSlack'; import createWebhookApi from 'api/channels/createWebhook'; +import testSlackApi from 'api/channels/testSlack'; +import testWebhookApi from 'api/channels/testWebhook'; import ROUTES from 'constants/routes'; import FormAlertChannels from 'container/FormAlertChannels'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { ChannelType, @@ -17,6 +20,9 @@ import { function CreateAlertChannels({ preType = 'slack', }: CreateAlertChannelsProps): JSX.Element { + // init namespace for translations + const { t } = useTranslation('channels'); + const [formInstance] = Form.useForm(); const [selectedConfig, setSelectedConfig] = useState< @@ -45,6 +51,7 @@ function CreateAlertChannels({ {{- end }}`, }); const [savingState, setSavingState] = useState(false); + const [testingState, setTestingState] = useState(false); const [notifications, NotificationElement] = notification.useNotification(); const [type, setType] = useState(preType); @@ -52,26 +59,26 @@ function CreateAlertChannels({ setType(value as ChannelType); }, []); - const onTestHandler = useCallback(() => { - console.log('test'); - }, []); + const prepareSlackRequest = useCallback(() => { + return { + api_url: selectedConfig?.api_url || '', + channel: selectedConfig?.channel || '', + name: selectedConfig?.name || '', + send_resolved: true, + text: selectedConfig?.text || '', + title: selectedConfig?.title || '', + }; + }, [selectedConfig]); const onSlackHandler = useCallback(async () => { try { setSavingState(true); - const response = await createSlackApi({ - api_url: selectedConfig?.api_url || '', - channel: selectedConfig?.channel || '', - name: selectedConfig?.name || '', - send_resolved: true, - text: selectedConfig?.text || '', - title: selectedConfig?.title || '', - }); + const response = await createSlackApi(prepareSlackRequest()); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Successfully created the channel', + description: t('channel_creation_done'), }); setTimeout(() => { history.replace(ROUTES.SETTINGS); @@ -79,21 +86,20 @@ function CreateAlertChannels({ } else { notifications.error({ message: 'Error', - description: response.error || 'Error while creating the channel', + description: response.error || t('channel_creation_failed'), }); } setSavingState(false); } catch (error) { notifications.error({ message: 'Error', - description: - 'An unexpected error occurred while creating this channel, please try again', + description: t('channel_creation_failed'), }); setSavingState(false); } - }, [notifications, selectedConfig]); + }, [prepareSlackRequest, t, notifications]); - const onWebhookHandler = useCallback(async () => { + const prepareWebhookRequest = useCallback(() => { // initial api request without auth params let request: WebhookChannel = { api_url: selectedConfig?.api_url || '', @@ -101,39 +107,42 @@ function CreateAlertChannels({ send_resolved: true, }; - setSavingState(true); - - try { - if (selectedConfig?.username !== '' || selectedConfig?.password !== '') { - if (selectedConfig?.username !== '') { - // if username is not null then password must be passed - if (selectedConfig?.password !== '') { - request = { - ...request, - username: selectedConfig.username, - password: selectedConfig.password, - }; - } else { - notifications.error({ - message: 'Error', - description: 'A Password must be provided with user name', - }); - } - } else if (selectedConfig?.password !== '') { - // only password entered, set bearer token + if (selectedConfig?.username !== '' || selectedConfig?.password !== '') { + if (selectedConfig?.username !== '') { + // if username is not null then password must be passed + if (selectedConfig?.password !== '') { request = { ...request, - username: '', + username: selectedConfig.username, password: selectedConfig.password, }; + } else { + notifications.error({ + message: 'Error', + description: t('username_no_password'), + }); } + } else if (selectedConfig?.password !== '') { + // only password entered, set bearer token + request = { + ...request, + username: '', + password: selectedConfig.password, + }; } + } + return request; + }, [notifications, t, selectedConfig]); + const onWebhookHandler = useCallback(async () => { + setSavingState(true); + try { + const request = prepareWebhookRequest(); const response = await createWebhookApi(request); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Successfully created the channel', + description: t('channel_creation_done'), }); setTimeout(() => { history.replace(ROUTES.SETTINGS); @@ -141,19 +150,17 @@ function CreateAlertChannels({ } else { notifications.error({ message: 'Error', - description: response.error || 'Error while creating the channel', + description: response.error || t('channel_creation_failed'), }); } } catch (error) { notifications.error({ message: 'Error', - description: - 'An unexpected error occurred while creating this channel, please try again', + description: t('channel_creation_failed'), }); } setSavingState(false); - }, [notifications, selectedConfig]); - + }, [prepareWebhookRequest, t, notifications]); const onSaveHandler = useCallback( async (value: ChannelType) => { switch (value) { @@ -166,11 +173,64 @@ function CreateAlertChannels({ default: notifications.error({ message: 'Error', - description: 'channel type selected is invalid', + description: t('selected_channel_invalid'), }); } }, - [onSlackHandler, onWebhookHandler, notifications], + [onSlackHandler, t, onWebhookHandler, notifications], + ); + + const performChannelTest = useCallback( + async (channelType: ChannelType) => { + setTestingState(true); + try { + let request; + let response; + switch (channelType) { + case WebhookType: + request = prepareWebhookRequest(); + response = await testWebhookApi(request); + break; + case SlackType: + request = prepareSlackRequest(); + response = await testSlackApi(request); + break; + default: + notifications.error({ + message: 'Error', + description: t('test_unsupported'), + }); + setTestingState(false); + return; + } + + if (response.statusCode === 200) { + notifications.success({ + message: 'Success', + description: t('channel_test_done'), + }); + } else { + notifications.error({ + message: 'Error', + description: t('channel_test_failed'), + }); + } + } catch (error) { + notifications.error({ + message: 'Error', + description: t('channel_test_unexpected'), + }); + } + setTestingState(false); + }, + [prepareWebhookRequest, t, prepareSlackRequest, notifications], + ); + + const onTestHandler = useCallback( + async (value: ChannelType) => { + performChannelTest(value); + }, + [performChannelTest], ); return ( @@ -183,8 +243,9 @@ function CreateAlertChannels({ onTestHandler, onSaveHandler, savingState, + testingState, NotificationElement, - title: 'New Notification Channels', + title: t('page_title_create'), initialValue: { type, ...selectedConfig, diff --git a/frontend/src/container/EditAlertChannels/index.tsx b/frontend/src/container/EditAlertChannels/index.tsx index e4aab19d31..b8db5a0e9b 100644 --- a/frontend/src/container/EditAlertChannels/index.tsx +++ b/frontend/src/container/EditAlertChannels/index.tsx @@ -1,6 +1,8 @@ import { Form, notification } from 'antd'; import editSlackApi from 'api/channels/editSlack'; import editWebhookApi from 'api/channels/editWebhook'; +import testSlackApi from 'api/channels/testSlack'; +import testWebhookApi from 'api/channels/testWebhook'; import ROUTES from 'constants/routes'; import { ChannelType, @@ -12,11 +14,15 @@ import { import FormAlertChannels from 'container/FormAlertChannels'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; function EditAlertChannels({ initialValue, }: EditAlertChannelsProps): JSX.Element { + // init namespace for translations + const { t } = useTranslation('channels'); + const [formInstance] = Form.useForm(); const [selectedConfig, setSelectedConfig] = useState< Partial @@ -24,6 +30,7 @@ function EditAlertChannels({ ...initialValue, }); const [savingState, setSavingState] = useState(false); + const [testingState, setTestingState] = useState(false); const [notifications, NotificationElement] = notification.useNotification(); const { id } = useParams<{ id: string }>(); @@ -35,9 +42,8 @@ function EditAlertChannels({ setType(value as ChannelType); }, []); - const onSlackEditHandler = useCallback(async () => { - setSavingState(true); - const response = await editSlackApi({ + const prepareSlackRequest = useCallback(() => { + return { api_url: selectedConfig?.api_url || '', channel: selectedConfig?.channel || '', name: selectedConfig?.name || '', @@ -45,12 +51,27 @@ function EditAlertChannels({ text: selectedConfig?.text || '', title: selectedConfig?.title || '', id, - }); + }; + }, [id, selectedConfig]); + + const onSlackEditHandler = useCallback(async () => { + setSavingState(true); + + if (selectedConfig?.api_url === '') { + notifications.error({ + message: 'Error', + description: t('webhook_url_required'), + }); + setSavingState(false); + return; + } + + const response = await editSlackApi(prepareSlackRequest()); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Channels Edited Successfully', + description: t('channel_edit_done'), }); setTimeout(() => { @@ -59,15 +80,27 @@ function EditAlertChannels({ } else { notifications.error({ message: 'Error', - description: response.error || 'error while updating the Channels', + description: response.error || t('channel_edit_failed'), }); } setSavingState(false); - }, [selectedConfig, notifications, id]); + }, [prepareSlackRequest, t, notifications, selectedConfig]); + + const prepareWebhookRequest = useCallback(() => { + const { name, username, password } = selectedConfig; + return { + api_url: selectedConfig?.api_url || '', + name: name || '', + send_resolved: true, + username, + password, + id, + }; + }, [id, selectedConfig]); const onWebhookEditHandler = useCallback(async () => { setSavingState(true); - const { name, username, password } = selectedConfig; + const { username, password } = selectedConfig; const showError = (msg: string): void => { notifications.error({ @@ -77,40 +110,33 @@ function EditAlertChannels({ }; if (selectedConfig?.api_url === '') { - showError('Webhook URL is mandatory'); + showError(t('webhook_url_required')); setSavingState(false); return; } if (username && (!password || password === '')) { - showError('Please enter a password'); + showError(t('username_no_password')); setSavingState(false); return; } - const response = await editWebhookApi({ - api_url: selectedConfig?.api_url || '', - name: name || '', - send_resolved: true, - username, - password, - id, - }); + const response = await editWebhookApi(prepareWebhookRequest()); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Channels Edited Successfully', + description: t('channel_edit_done'), }); setTimeout(() => { history.replace(ROUTES.SETTINGS); }, 2000); } else { - showError(response.error || 'error while updating the Channels'); + showError(response.error || t('channel_edit_failed')); } setSavingState(false); - }, [selectedConfig, notifications, id]); + }, [prepareWebhookRequest, t, notifications, selectedConfig]); const onSaveHandler = useCallback( (value: ChannelType) => { @@ -123,9 +149,58 @@ function EditAlertChannels({ [onSlackEditHandler, onWebhookEditHandler], ); - const onTestHandler = useCallback(() => { - console.log('test'); - }, []); + const performChannelTest = useCallback( + async (channelType: ChannelType) => { + setTestingState(true); + try { + let request; + let response; + switch (channelType) { + case WebhookType: + request = prepareWebhookRequest(); + response = await testWebhookApi(request); + break; + case SlackType: + request = prepareSlackRequest(); + response = await testSlackApi(request); + break; + default: + notifications.error({ + message: 'Error', + description: t('test_unsupported'), + }); + setTestingState(false); + return; + } + + if (response.statusCode === 200) { + notifications.success({ + message: 'Success', + description: t('channel_test_done'), + }); + } else { + notifications.error({ + message: 'Error', + description: t('channel_test_failed'), + }); + } + } catch (error) { + notifications.error({ + message: 'Error', + description: t('channel_test_failed'), + }); + } + setTestingState(false); + }, + [prepareWebhookRequest, t, prepareSlackRequest, notifications], + ); + + const onTestHandler = useCallback( + async (value: ChannelType) => { + performChannelTest(value); + }, + [performChannelTest], + ); return ( - + { setSelectedConfig((value) => ({ @@ -22,8 +25,8 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element { @@ -35,7 +38,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element { /> - +