Compare commits

..

101 Commits

Author SHA1 Message Date
Shaheer Kochai
1b48eb81c6 feat: add timezone support to the graphs throughout the app (#6520)
...
2024-12-03 13:56:48 +04:30
ahmadshaheer
504811546e chore: added null check for timezone.value in updateTimezone 2024-12-02 19:40:27 +04:30
ahmadshaheer
e012f10395 chore: overall improvements 2024-11-20 18:42:41 +04:30
ahmadshaheer
676e32ea09 chore: store timezone string in local storage instead of object 2024-11-20 18:20:48 +04:30
ahmadshaheer
28045772b8 fix: get active timezone in timepicker hint 2024-11-20 09:23:52 +04:30
ahmadshaheer
b1120c7d16 fix: if timezone is undefined, fallback to browser time zone 2024-11-20 09:19:55 +04:30
ahmadshaheer
55c9205aad feat: timezone setting functionality in timepicker, timezone picker, and timezone preferences 2024-11-20 09:19:55 +04:30
ahmadshaheer
5b4f423f9f feat: create a context for timezone and handle the timezone configuration 2024-11-20 09:19:16 +04:30
ahmadshaheer
cc376ce6a8 chore: improve timezoneUtils by adding a function to get browser timezone 2024-11-20 09:19:16 +04:30
ahmadshaheer
20e00c597a fix: display the timezone in timepicker hint 'You are at' 2024-11-20 09:18:43 +04:30
ahmadshaheer
ff7da5c05b fix: fix the issue of timezone breaking for browser and utc timezones 2024-11-19 18:42:20 +04:30
ahmadshaheer
14ccadaeb5 fix: don't focus on time picker when timezone is clicked 2024-11-19 13:55:42 +04:30
ahmadshaheer
984f3829dd chore: fix the typo 2024-11-19 13:37:18 +04:30
ahmadshaheer
31a9ead2fc feat: display timezone in timepicker input 2024-11-19 13:34:15 +04:30
ahmadshaheer
65ce8eaf14 chore: change timezone item from div to button 2024-11-19 13:33:14 +04:30
ahmadshaheer
daec491c79 chore: improve timezone utils 2024-11-19 13:29:33 +04:30
ahmadshaheer
49e29567f4 feat: timezone preferences UI 2024-11-19 10:02:00 +04:30
ahmadshaheer
8edd5fe7d6 fix: overall improvement + add searchIndex to timezone 2024-11-18 18:49:22 +04:30
ahmadshaheer
178a3153dd chore: add the selected timezone as url param and close timezone picker on select 2024-11-18 18:38:45 +04:30
ahmadshaheer
e7f1b27a5b feat: add support for esc keypress to close the timezone picker 2024-11-18 18:27:38 +04:30
ahmadshaheer
dbf0f236be feat: time picker hint and timezone picker UI with basic functionality + helper to get timezones 2024-11-18 17:47:48 +04:30
Vikrant Gupta
d93f72f18d chore: use the license v2 key to fill licenses v3 on startup (#6468)
* feat: use the license v2 key to fill licenses v3 on startup

* chore: make the init only if the licenses v2 is present

* chore: address review comments
2024-11-18 17:55:00 +05:30
Shaheer Kochai
a59e7b9dfb feat: add 'create channel' option in channels list and refetch alert channels on opening the channels dropdown (#6416)
* feat: add channel creation option and auto-refresh channels list on dropdown open

* chore: move inline styles to style.ts

* fix: show the prompt to ask admin if the user doesn't have permissions

* fix: display create channel option only if the user has permission

* fix: prevent repeated new alert event logs + log new channel option inside dropdown
2024-11-18 06:30:06 +00:00
Nityananda Gohain
91bbeaf175 fix: remove unwanted trace API's (#6464) 2024-11-18 10:27:08 +05:30
Yunus M
22e61e1605 [Snyk] Security upgrade alpine from 3.18.6 to 3.20.3 (#6463)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-6913411
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249236
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249265
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249265
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249419

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-11-17 21:56:15 +05:30
Srikanth Chekuri
656d1c2b1c chore: add missing alert telemetry (#6459) 2024-11-16 19:16:05 +00:00
Srikanth Chekuri
493ae4fd07 chore: add user email to log_comment (#6461) 2024-11-17 00:36:10 +05:30
Srikanth Chekuri
cd1ec561b1 fix: compare op outside bounds for anomaly alert (#6458) 2024-11-16 20:17:34 +05:30
Nityananda Gohain
0acf39a532 feat: support for new enrichment logic in traces (#6438)
* feat: support for new enrichment logic in traces

* fix: default test added

* fix: update func name in links

* Update pkg/query-service/utils/logs_test.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2024-11-16 15:19:25 +05:30
Nityananda Gohain
35f4eaa23b fix: update logs struct to fix live logs (#6453) 2024-11-15 22:42:16 +05:30
Nityananda Gohain
77c5f17dce feat: support for window based pagination in new trace v4 (#6440)
* feat: support for window based pagination in new trace v4

* fix: update pagination logic

* fix: update comment

* fix: substract correct length

* fix: revert changes

* fix: add tests for querier

* fix: rename matcher

* fix: handle offset inmemory for list queries in traces

* fix: correct var name

* fix: add max pagination limit for traces
2024-11-15 22:13:28 +05:30
SagarRajput-7
c1478c4e54 feat: removed dashboard uuid is all cases be it duplicate, empty or somevalid, while import json (#6448)
* feat: removed dashboard uuid is all cases be it duplicate, empty or somevalid, while import json

* feat: added comment to better explain the logic
2024-11-15 19:35:29 +05:30
Yunus M
371224a64a fix: show org onboarding only to cloud customers (#6451) 2024-11-15 19:12:38 +05:30
Yunus M
504bc0d541 feat: ingestion limits - add toggle feature (#6430) 2024-11-15 08:32:31 +00:00
Nityananda Gohain
2faa0c6d4f feat: trace V4 QB (#6407)
* feat: trace V4 QB

* fix: update get column name and remove id

* fix: handle contains and update tests

* fix: remove unwanted step interval calculation

* fix: add test cases

* fix: add tests for static columns in QB

* fix: add more order by tests

* fix: update order by logic
2024-11-13 20:30:01 +05:30
Srikanth Chekuri
969ac5028e chore: add v2 metric writer to pipelines (#6345) 2024-11-13 10:41:28 +00:00
Srikanth Chekuri
323da3494b chore: add experimental rate/increase calc (#6432) 2024-11-13 11:47:56 +05:30
Vikrant Gupta
01fda51959 chore: return proper http codes on unique constraint error (#6428) 2024-11-13 00:25:00 +05:30
Srikanth Chekuri
85ac21f253 fix: update request payload for span metrics queries (#6323) 2024-11-12 17:22:42 +00:00
Srikanth Chekuri
fd9e9f0fb3 chore: add k8s {deployment, daemonset, statefulset, job} resources (#6401) 2024-11-12 15:23:40 +00:00
Nityananda Gohain
d5523fc092 fix: ignore ts for panel type table (#6419) 2024-11-12 08:04:45 +00:00
Nityananda Gohain
2ec641b99e fix: add severity_text legend (#6415) 2024-11-12 05:54:22 +00:00
Ekansh Gupta
d1503f1418 feat: fixProducerAPI (#6422)
chore: bugfix
2024-11-12 05:30:36 +00:00
Vikrant Gupta
e974e9d47f feat: consume the new licenses v3 structure. (#6341)
* feat: setup for licenses v3 integration

* feat: added some more logic

* feat: validator changes

* chore: added a couple of todos

* feat: added config parameter for licenses v3 and the boot option

* feat: some typo fix

* feat: added refresh licenses handler

* feat: handle the start manager license activation

* chore: text updates

* feat: added list licenses call

* chore: refactor the entire code to cleanup interfaces

* fix: nil pointer error

* chore: some minor edits

* feat: model changes

* feat: model changes

* fix: utilise factory pattern

* feat: added default basic plan

* chore: added test cases for new license function

* feat: added more test cases

* chore: make the licenses id not null

* feat: cosmetic changes

* feat: cosmetic changes

* feat: update zeus URL

* chore: license testing fixes

* feat: added license status and category handling for query-service

* chore: added v3 support in v2 endpoint

* chore: http response codes and some code cleanup

* chore: added detailed test cases

* chore: address review comments

* chore: some misc cleanup
2024-11-12 01:40:10 +05:30
Shaheer Kochai
577a169508 feat: alert rename interaction (#6208)
* feat: alert rename interaction

* feat: add support for enter and escape shortcuts to submit and cancel rename

* chore: add missing alert field

* chore: update the style similar to dashboard rename

* refactor: remove buttonProps

* chore: add missing alert property to fix the build

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2024-11-11 19:13:07 +00:00
Shaheer Kochai
939e2a3570 fix: fix the issue of adding new query in new alert page changing the data source (#6286)
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2024-11-11 07:34:20 +00:00
Yunus M
b64326070c [Snyk] Fix for 2 vulnerabilities (#6215)
* fix: frontend/package.json & frontend/yarn.lock to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-UPLOT-6209224
- https://snyk.io/vuln/SNYK-JS-VUETEMPLATECOMPILER-8219888

* chore: upgrade design tokens to 1.1.3

---------

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: ahmadshaheer <ashaheerki@gmail.com>
2024-11-11 06:45:43 +00:00
SagarRajput-7
63872983c6 feat: added metric page in messaging queues (#6399)
* feat: added metric page in messaging queues

* feat: added misc fixes

* feat: removed a class name from mqcards

* feat: added lightMode styles for kafka 2.0 (#6400)

* feat: resolved comments and used strings
2024-11-09 13:04:43 +05:30
Srikanth Chekuri
831540eaf0 fix: test notification missing for anomaly alert (#6391) 2024-11-08 15:40:09 +00:00
Nityananda Gohain
22c10f9479 Issue 6367 (#6376)
* fix: issue with orderby by materialized column

* fix: tests

* fix: order by issue in old explorer as well
2024-11-08 07:05:32 +00:00
Yunus M
e748fb0655 chore: update events for onboarding part 2 (#6397) 2024-11-08 06:52:39 +00:00
SagarRajput-7
fdc54a62a9 fix: kafka - misc fix and features (#6379)
* feat: fixed multiple fixes and chores in kafka 2.0

* feat: fixed producer latency - producer-detail call

* feat: fixed mq-detail page layout and pagination

* feat: resolved comments
2024-11-07 23:49:47 +05:30
SagarRajput-7
abe0ab69b0 feat: added kafka - scenario - 4 - drop rate table (#6380)
* feat: added kafka - scenario - 4 - drop rate table

* feat: added api, new table and traceid redirection

* feat: code refactor
2024-11-07 23:37:54 +05:30
Shaheer Kochai
e623c92615 fix: adding the key requires double enter before it gets added as label key after the first label (#6296) 2024-11-07 15:03:59 +00:00
Shaheer Kochai
dc5917db01 chore: setup router compatibility package (#6285) 2024-11-07 19:02:23 +04:30
dependabot[bot]
d6a7f0b6f4 chore(deps): bump express from 4.19.2 to 4.21.1 in /frontend (#6166)
Bumps [express](https://github.com/expressjs/express) from 4.19.2 to 4.21.1.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-07 11:48:05 +05:30
Vikrant Gupta
471803115e feat: added support for instrumentation scope in logs (#6378)
* feat: added support for instrumentation scope in logs

* chore: remove console logs

* fix: the logic for rendering prefix

* feat: address review comments

---------

Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
2024-11-07 11:47:35 +05:30
SagarRajput-7
8403a3362d feat: corrected the handling of relativeTime as null in alertHistory page (#6392) 2024-11-07 02:34:11 +05:30
Nityananda Gohain
64d46bc855 feat: support for scope in logs old and new qb (#6339) 2024-11-06 20:47:04 +05:30
SagarRajput-7
c9fee27604 feat: updated the design for Messaging Queue - summary section (#6319)
* feat: updated the design for Messaging Queue - summary section

* feat: resolved comments

* feat: added better logic for switch options and resolved query
2024-11-06 14:44:39 +05:30
SagarRajput-7
f1b6b2d3d8 feat: added onboarding detail for consumer setup (#6372)
* feat: added onboarding detail for consumer setup

* feat: corrected spelling

* feat: reorders imports correctly
2024-11-06 14:43:54 +05:30
SagarRajput-7
468f056530 feat: added kafka - scenario - 4 (#6370)
* feat: added kafka - scenario - 4

* feat: added error handling and correct api handler for kafka apis
2024-11-06 14:24:38 +05:30
SagarRajput-7
7086470ce2 feat: added healthcheck and attribute checklist component for Kafka (#6371)
* feat: added healthcheck and attribute checklist component for Kafka

* feat: corrected the onboardingapi payload

* feat: added missing configuration button at overview and onboarding flow
2024-11-06 14:23:51 +05:30
Yunus M
352296c6cd fix: initialize target to 3 in anomaly detection alert (#6362) 2024-11-05 22:13:12 +05:30
SagarRajput-7
975307a8b8 feat: added onboarding setup for Producer for Messaging queues (#6236)
* fix: added onboarding setup for producer/consumer for Messaging queues

* fix: polled onboarding status api

* feat: added onboarding status api with useQueury functions and updated endpoints

* feat: added onboarding status api util for attribute data

* feat: refactoring and url query changes

* feat: changed start and end time to nanosecond for api payload

* feat: added comment description
2024-11-05 19:40:23 +05:30
SagarRajput-7
12377be809 feat: added generic UI for scenario 1,3,4 (#6287)
* feat: added generic table component for scenario 1,3,4

* feat: added generic logic for mq detail tables and consumed for sc-1,2

* feat: added overview and details table for scenario-3

* feat: added table row clicks func

* feat: resolved comments
2024-11-05 19:26:41 +05:30
Yunus M
9d90b8d19c chore: github wf update pr labels and block pr until related docs are shipped for the feature (#6333) 2024-11-04 23:58:38 +05:30
Yunus M
5005923ef4 fix: re add threshold for promql alerts (#6355) 2024-11-04 15:19:05 +05:30
Srikanth Chekuri
db4338be42 chore: add feature flag, handle out-of-index error, some house keeping work (#6344) 2024-11-02 01:23:43 +05:30
Yunus M
c7d0598ec0 feat: improve async handling for org onboarding cases (#6342) 2024-11-01 23:55:29 +05:30
Yunus M
4978fb9599 fix: add safety check to check if anomaly rule in uplot chart options (#6343) 2024-11-01 22:51:09 +05:30
Shivanshu Raj Shrivastava
7b18c3ba06 enable scenario 4 on staging (#6269)
* fix: enable env at docker compose
2024-11-01 21:19:58 +05:30
Shaheer Kochai
92cdb36879 fix: redirect to docs on clicking alert setup guide in create alert page (#6265) 2024-11-01 17:03:59 +05:30
Nityananda Gohain
580f0b816e fix: issues with resource query builder w.r.t quotes (#6318) 2024-11-01 13:52:13 +05:30
Shivanshu Raj Shrivastava
b770fc2457 fix: typo (#6334) 2024-10-31 20:11:50 +05:30
dependabot[bot]
c177230cce chore(deps): bump webpack from 5.88.2 to 5.94.0 in /frontend (#5813)
Bumps [webpack](https://github.com/webpack/webpack) from 5.88.2 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.88.2...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-31 16:44:26 +05:30
Ankit Nayan
2112047a02 [Snyk] Security upgrade alpine from 3.18.5 to 3.20.3 (#6237)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-6913411
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249265
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249419
- https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6152404
- https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6152404

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-10-31 15:54:28 +05:30
Shaheer Kochai
03c193d5a1 chore: upgrade axios from 1.7.4 to 1.7.7 (#6291) 2024-10-31 09:00:34 +00:00
Yunus M
b83b295318 fix: frontend/package.json & frontend/yarn.lock to reduce vulnerabilities (#6266)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-HTTPPROXYMIDDLEWARE-8229906
- https://snyk.io/vuln/SNYK-JS-UPLOT-6209224

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-10-31 14:10:49 +05:30
Shivanshu Raj Shrivastava
fbe75cd057 fix: use query builder for metrics onboarding API (#6327) 2024-10-30 23:13:56 +05:30
Yunus M
860145fb1d fix: handle redirect in onboarding (#6324) 2024-10-30 15:00:01 +00:00
dependabot[bot]
2fe75e74cd chore(deps): bump uplot from 1.6.26 to 1.6.31 in /frontend
Bumps [uplot](https://github.com/leeoniya/uPlot) from 1.6.26 to 1.6.31.
- [Release notes](https://github.com/leeoniya/uPlot/releases)
- [Commits](https://github.com/leeoniya/uPlot/compare/1.6.26...1.6.31)

---
updated-dependencies:
- dependency-name: uplot
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 16:59:58 +05:30
Yunus M
8e19c346a4 feat: handle light mode and remove unnecessary logos 2024-10-30 15:41:51 +05:30
Yunus M
1b33efe4cc chore: remove workspace from fullscreen 2024-10-30 15:41:51 +05:30
Yunus M
2642338672 chore: update org onboarding package 2024-10-30 15:41:51 +05:30
Yunus M
845dc00568 feat: handle onboarding visibility 2024-10-30 15:41:51 +05:30
Yunus M
a1090bfdc5 feat: handle invite user flows 2024-10-30 15:41:51 +05:30
Yunus M
44f41c55f9 feat: handle linear to exponential conversion for logs, services and hosts 2024-10-30 15:41:51 +05:30
Yunus M
42ac9ab6fe feat: update to use v2 instance 2024-10-30 15:41:51 +05:30
Yunus M
5c02250aae feat: feedback updates 2024-10-30 15:41:51 +05:30
Yunus M
c49a9dac1a feat: feedback updates 2024-10-30 15:41:51 +05:30
Yunus M
abc2ec2155 feat: handle redirection after onboarding 2024-10-30 15:41:51 +05:30
Yunus M
4dc5615d2f feat: handle errors for profiles and invite users api 2024-10-30 15:41:51 +05:30
Yunus M
6c350f30aa feat: integrate update profile and invite users api 2024-10-30 15:41:51 +05:30
Yunus M
6664e1bc02 feat: maintain state and add log events 2024-10-30 15:41:51 +05:30
Yunus M
438cbcef87 feat: add questionaire components (#5998)
* feat: add questionaire components

* feat: update css

* feat: delete icon svgs and update css

* feat: update component names
2024-10-30 15:41:51 +05:30
Yunus M
829e1f0920 feat: onboarding v2 base setup 2024-10-30 15:41:51 +05:30
Srikanth Chekuri
68d25a8989 fix: add support for {{.Labels.<key>}} with dots in key for template (#6282) 2024-10-30 14:12:45 +05:30
Vikrant Gupta
cc90321ac0 chore: move hostname to resource attributes for logs qf (#6303)
* chore: move hostname to resource attributes for logs qf

* chore: fix the key for hostname
2024-10-29 13:18:34 +05:30
Ekansh Gupta
bdcae62bf9 fix: fixed the step interval which was being perculated to list view (#6260)
* fix: fixed the step interval which was being perculated to list view

* fix: fixed the step interval which was being perculated to list view

* fix: fixed the step interval which was being perculated to list view

* fix: fixed the step interval which was being perculated to list view

* fix: fixed the step interval which was being perculated to list view

* fix: fixed the step interval which was being perculated to list view

* chore: bump signoz-otel-collector version (#6290)

* Chore: bump signoz otel collector dependency to 0.111.5 (#6302)

* chore: bump signoz-otel-collector dependency version to 0.111.5

* chore: logs filter suggestions: update import for ResourceHierarchy from signoz-otel-collector

* fix: fixed the step interval which was being perculated to list view

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
Co-authored-by: Raj Kamal Singh <1133322+raj-k-singh@users.noreply.github.com>
2024-10-28 22:32:04 +05:30
Raj Kamal Singh
4e26189778 Chore: bump signoz otel collector dependency to 0.111.5 (#6302)
* chore: bump signoz-otel-collector dependency version to 0.111.5

* chore: logs filter suggestions: update import for ResourceHierarchy from signoz-otel-collector
2024-10-28 21:23:47 +05:30
248 changed files with 14971 additions and 4646 deletions

83
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: "Update PR labels and Block PR until related docs are shipped for the feature"
on:
pull_request:
branches:
- develop
types: [opened, edited, labeled, unlabeled]
permissions:
pull-requests: write
contents: read
jobs:
docs_label_check:
runs-on: ubuntu-latest
steps:
- name: Check PR Title and Manage Labels
uses: actions/github-script@v6
with:
script: |
const prTitle = context.payload.pull_request.title;
const prNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Fetch the current PR details to get labels
const pr = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
});
const labels = pr.data.labels.map(label => label.name);
if (prTitle.startsWith('feat:')) {
const hasDocsRequired = labels.includes('docs required');
const hasDocsShipped = labels.includes('docs shipped');
const hasDocsNotRequired = labels.includes('docs not required');
// If "docs not required" is present, skip the checks
if (hasDocsNotRequired && !hasDocsRequired) {
console.log("Skipping checks due to 'docs not required' label.");
return; // Exit the script early
}
// If "docs shipped" is present, remove "docs required" if it exists
if (hasDocsShipped && hasDocsRequired) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: 'docs required'
});
console.log("Removed 'docs required' label.");
}
// Add "docs required" label if neither "docs shipped" nor "docs required" are present
if (!hasDocsRequired && !hasDocsShipped) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: ['docs required']
});
console.log("Added 'docs required' label.");
}
}
// Fetch the updated labels after any changes
const updatedPr = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
});
const updatedLabels = updatedPr.data.labels.map(label => label.name);
const updatedHasDocsRequired = updatedLabels.includes('docs required');
const updatedHasDocsShipped = updatedLabels.includes('docs shipped');
// Block PR if "docs required" is still present and "docs shipped" is missing
if (updatedHasDocsRequired && !updatedHasDocsShipped) {
core.setFailed("This PR requires documentation. Please remove the 'docs required' label and add the 'docs shipped' label to proceed.");
}

View File

@@ -31,7 +31,6 @@ jobs:
GCP_ZONE: ${{ secrets.GCP_ZONE }}
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
KAFKA_SPAN_EVAL: true
run: |
read -r -d '' COMMAND <<EOF || true
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
@@ -39,6 +38,7 @@ jobs:
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
export OTELCOL_TAG="main"
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
export KAFKA_SPAN_EVAL="true"
docker system prune --force
docker pull signoz/signoz-otel-collector:main
docker pull signoz/signoz-schema-migrator:main

View File

@@ -66,28 +66,6 @@ processors:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
timeout: 2s
signozspanmetrics/cumulative:
metrics_exporter: clickhousemetricswrite
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
dimensions:
- name: service.namespace
default: default
- name: deployment.environment
default: default
# This is added to ensure the uniqueness of the timeseries
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: signoz.collector.id
- name: service.version
- name: browser.platform
- name: browser.mobile
- name: k8s.cluster.name
- name: k8s.node.name
- name: k8s.namespace.name
- name: host.name
- name: host.type
- name: container.name
# memory_limiter:
# # 80% of maximum memory up to 2G
# limit_mib: 1500
@@ -138,6 +116,8 @@ exporters:
enabled: true
clickhousemetricswrite/prometheus:
endpoint: tcp://clickhouse:9000/signoz_metrics
clickhousemetricswritev2:
dsn: tcp://clickhouse:9000/signoz_metrics
# logging: {}
clickhouselogsexporter:
dsn: tcp://clickhouse:9000/signoz_logs
@@ -161,20 +141,20 @@ service:
pipelines:
traces:
receivers: [jaeger, otlp]
processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch]
processors: [signozspanmetrics/delta, batch]
exporters: [clickhousetraces]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [clickhousemetricswrite]
metrics/generic:
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
metrics/hostmetrics:
receivers: [hostmetrics]
processors: [resourcedetection, batch]
exporters: [clickhousemetricswrite]
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
metrics/prometheus:
receivers: [prometheus]
processors: [batch]
exporters: [clickhousemetricswrite/prometheus]
exporters: [clickhousemetricswrite/prometheus, clickhousemetricswritev2]
logs:
receivers: [otlp, tcplog/docker]
processors: [batch]

View File

@@ -191,6 +191,7 @@ services:
- GODEBUG=netdns=go
- TELEMETRY_ENABLED=true
- DEPLOYMENT_TYPE=docker-standalone-amd
- KAFKA_SPAN_EVAL=${KAFKA_SPAN_EVAL:-false}
restart: on-failure
healthcheck:
test:

View File

@@ -57,35 +57,11 @@ receivers:
labels:
job_name: otel-collector
processors:
batch:
send_batch_size: 10000
send_batch_max_size: 11000
timeout: 10s
signozspanmetrics/cumulative:
metrics_exporter: clickhousemetricswrite
metrics_flush_interval: 60s
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
dimensions:
- name: service.namespace
default: default
- name: deployment.environment
default: default
# This is added to ensure the uniqueness of the timeseries
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: signoz.collector.id
- name: service.version
- name: browser.platform
- name: browser.mobile
- name: k8s.cluster.name
- name: k8s.node.name
- name: k8s.namespace.name
- name: host.name
- name: host.type
- name: container.name
# memory_limiter:
# # 80% of maximum memory up to 2G
# limit_mib: 1500
@@ -149,6 +125,8 @@ exporters:
enabled: true
clickhousemetricswrite/prometheus:
endpoint: tcp://clickhouse:9000/signoz_metrics
clickhousemetricswritev2:
dsn: tcp://clickhouse:9000/signoz_metrics
clickhouselogsexporter:
dsn: tcp://clickhouse:9000/signoz_logs
timeout: 10s
@@ -168,20 +146,20 @@ service:
pipelines:
traces:
receivers: [jaeger, otlp]
processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch]
processors: [signozspanmetrics/delta, batch]
exporters: [clickhousetraces]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [clickhousemetricswrite]
metrics/generic:
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
metrics/hostmetrics:
receivers: [hostmetrics]
processors: [resourcedetection, batch]
exporters: [clickhousemetricswrite]
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
metrics/prometheus:
receivers: [prometheus]
processors: [batch]
exporters: [clickhousemetricswrite/prometheus]
exporters: [clickhousemetricswrite/prometheus, clickhousemetricswritev2]
logs:
receivers: [otlp, tcplog/docker]
processors: [batch]

View File

@@ -1,5 +1,5 @@
# use a minimal alpine image
FROM alpine:3.18.6
FROM alpine:3.20.3
# Add Maintainer Info
LABEL maintainer="signoz"

View File

@@ -40,6 +40,7 @@ type APIHandlerOptions struct {
// Querier Influx Interval
FluxInterval time.Duration
UseLogsNewSchema bool
UseLicensesV3 bool
}
type APIHandler struct {
@@ -65,6 +66,7 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
Cache: opts.Cache,
FluxInterval: opts.FluxInterval,
UseLogsNewSchema: opts.UseLogsNewSchema,
UseLicensesV3: opts.UseLicensesV3,
})
if err != nil {
@@ -173,10 +175,25 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
// v2
router.HandleFunc("/api/v2/licenses",
am.ViewAccess(ah.listLicensesV2)).
Methods(http.MethodGet)
// v3
router.HandleFunc("/api/v3/licenses",
am.ViewAccess(ah.listLicensesV3)).
Methods(http.MethodGet)
router.HandleFunc("/api/v3/licenses",
am.AdminAccess(ah.applyLicenseV3)).
Methods(http.MethodPost)
router.HandleFunc("/api/v3/licenses",
am.AdminAccess(ah.refreshLicensesV3)).
Methods(http.MethodPut)
// v4
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
// Gateway

View File

@@ -9,6 +9,7 @@ import (
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
"go.signoz.io/signoz/pkg/http/render"
"go.uber.org/zap"
)
@@ -59,6 +60,21 @@ type billingDetails struct {
} `json:"data"`
}
type ApplyLicenseRequest struct {
LicenseKey string `json:"key"`
}
type ListLicenseResponse map[string]interface{}
func convertLicenseV3ToListLicenseResponse(licensesV3 []*model.LicenseV3) []ListLicenseResponse {
listLicenses := []ListLicenseResponse{}
for _, license := range licensesV3 {
listLicenses = append(listLicenses, license.Data)
}
return listLicenses
}
func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
licenses, apiError := ah.LM().GetLicenses(context.Background())
if apiError != nil {
@@ -88,6 +104,51 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
ah.Respond(w, license)
}
func (ah *APIHandler) listLicensesV3(w http.ResponseWriter, r *http.Request) {
licenses, apiError := ah.LM().GetLicensesV3(r.Context())
if apiError != nil {
RespondError(w, apiError, nil)
return
}
ah.Respond(w, convertLicenseV3ToListLicenseResponse(licenses))
}
// this function is called by zeus when inserting licenses in the query-service
func (ah *APIHandler) applyLicenseV3(w http.ResponseWriter, r *http.Request) {
var licenseKey ApplyLicenseRequest
if err := json.NewDecoder(r.Body).Decode(&licenseKey); err != nil {
RespondError(w, model.BadRequest(err), nil)
return
}
if licenseKey.LicenseKey == "" {
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
return
}
_, apiError := ah.LM().ActivateV3(r.Context(), licenseKey.LicenseKey)
if apiError != nil {
RespondError(w, apiError, nil)
return
}
render.Success(w, http.StatusAccepted, nil)
}
func (ah *APIHandler) refreshLicensesV3(w http.ResponseWriter, r *http.Request) {
apiError := ah.LM().RefreshLicense(r.Context())
if apiError != nil {
RespondError(w, apiError, nil)
return
}
render.Success(w, http.StatusNoContent, nil)
}
func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
type checkoutResponse struct {
@@ -154,11 +215,45 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
ah.Respond(w, billingResponse.Data)
}
func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
licensesV2 := []model.License{}
for _, l := range licenses {
licenseV2 := model.License{
Key: l.Key,
ActivationId: "",
PlanDetails: "",
FeatureSet: l.Features,
ValidationMessage: "",
IsCurrent: l.IsCurrent,
LicensePlan: model.LicensePlan{
PlanKey: l.PlanName,
ValidFrom: l.ValidFrom,
ValidUntil: l.ValidUntil,
Status: l.Status},
}
licensesV2 = append(licensesV2, licenseV2)
}
return licensesV2
}
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
licenses, apiError := ah.LM().GetLicenses(context.Background())
if apiError != nil {
RespondError(w, apiError, nil)
var licenses []model.License
if ah.UseLicensesV3 {
licensesV3, err := ah.LM().GetLicensesV3(r.Context())
if err != nil {
RespondError(w, err, nil)
return
}
licenses = convertLicenseV3ToLicenseV2(licensesV3)
} else {
_licenses, apiError := ah.LM().GetLicenses(r.Context())
if apiError != nil {
RespondError(w, apiError, nil)
return
}
licenses = _licenses
}
resp := model.Licenses{

View File

@@ -31,7 +31,6 @@ import (
"go.signoz.io/signoz/ee/query-service/rules"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/migrate"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
licensepkg "go.signoz.io/signoz/ee/query-service/license"
@@ -78,6 +77,7 @@ type ServerOptions struct {
Cluster string
GatewayUrl string
UseLogsNewSchema bool
UseLicensesV3 bool
}
// Server runs HTTP api service
@@ -134,7 +134,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
// initiate license manager
lm, err := licensepkg.StartManager("sqlite", localDB)
lm, err := licensepkg.StartManager("sqlite", localDB, serverOptions.UseLicensesV3)
if err != nil {
return nil, err
}
@@ -270,6 +270,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
FluxInterval: fluxInterval,
Gateway: gatewayProxy,
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
UseLicensesV3: serverOptions.UseLicensesV3,
}
apiHandler, err := api.NewAPIHandler(apiOpts)
@@ -348,7 +349,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
}
if user.User.OrgId == "" {
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
return nil, basemodel.UnauthorizedError(errors.New("orgId is missing in the claims"))
}
return user, nil
@@ -765,8 +766,9 @@ func makeRulesManager(
Cache: cache,
EvalDelay: baseconst.GetEvalDelay(),
PrepareTaskFunc: rules.PrepareTaskFunc,
UseLogsNewSchema: useLogsNewSchema,
PrepareTaskFunc: rules.PrepareTaskFunc,
PrepareTestRuleFunc: rules.TestNotification,
UseLogsNewSchema: useLogsNewSchema,
}
// create Manager

View File

@@ -13,6 +13,7 @@ var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
var FetchFeatures = GetOrDefaultEnv("FETCH_FEATURES", "false")
var ZeusFeaturesURL = GetOrDefaultEnv("ZEUS_FEATURES_URL", "ZeusFeaturesURL")
var ZeusURL = GetOrDefaultEnv("ZEUS_URL", "ZeusURL")
func GetOrDefaultEnv(key string, fallback string) string {
v := os.Getenv(key)

View File

@@ -13,3 +13,8 @@ type ActivationResponse struct {
ActivationId string `json:"ActivationId"`
PlanDetails string `json:"PlanDetails"`
}
type ValidateLicenseResponse struct {
Status status `json:"status"`
Data map[string]interface{} `json:"data"`
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"time"
"github.com/pkg/errors"
"go.uber.org/zap"
@@ -23,12 +24,14 @@ const (
)
type Client struct {
Prefix string
Prefix string
GatewayUrl string
}
func New() *Client {
return &Client{
Prefix: constants.LicenseSignozIo,
Prefix: constants.LicenseSignozIo,
GatewayUrl: constants.ZeusURL,
}
}
@@ -116,6 +119,60 @@ func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError)
}
func ValidateLicenseV3(licenseKey string) (*model.LicenseV3, *model.ApiError) {
// Creating an HTTP client with a timeout for better control
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("GET", C.GatewayUrl+"/v2/licenses/me", nil)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, fmt.Sprintf("failed to create request: %w", err)))
}
// Setting the custom header
req.Header.Set("X-Signoz-Cloud-Api-Key", licenseKey)
response, err := client.Do(req)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, fmt.Sprintf("failed to make post request: %w", err)))
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, fmt.Sprintf("failed to read validation response from %v", C.GatewayUrl)))
}
defer response.Body.Close()
switch response.StatusCode {
case 200:
a := ValidateLicenseResponse{}
err = json.Unmarshal(body, &a)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
}
license, err := model.NewLicenseV3(a.Data)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "failed to generate new license v3"))
}
return license, nil
case 400:
return nil, model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
fmt.Sprintf("bad request error received from %v", C.GatewayUrl)))
case 401:
return nil, model.Unauthorized(errors.Wrap(fmt.Errorf(string(body)),
fmt.Sprintf("unauthorized request error received from %v", C.GatewayUrl)))
default:
return nil, model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
fmt.Sprintf("internal request error received from %v", C.GatewayUrl)))
}
}
func NewPostRequestWithCtx(ctx context.Context, url string, contentType string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, POST, url, body)
if err != nil {

View File

@@ -3,10 +3,12 @@ package license
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"
"github.com/jmoiron/sqlx"
"github.com/mattn/go-sqlite3"
"go.signoz.io/signoz/ee/query-service/license/sqlite"
"go.signoz.io/signoz/ee/query-service/model"
@@ -48,6 +50,34 @@ func (r *Repo) GetLicenses(ctx context.Context) ([]model.License, error) {
return licenses, nil
}
func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
licensesData := []model.LicenseDB{}
licenseV3Data := []*model.LicenseV3{}
query := "SELECT id,key,data FROM licenses_v3"
err := r.db.Select(&licensesData, query)
if err != nil {
return nil, fmt.Errorf("failed to get licenses from db: %v", err)
}
for _, l := range licensesData {
var licenseData map[string]interface{}
err := json.Unmarshal([]byte(l.Data), &licenseData)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal data into licenseData : %v", err)
}
license, err := model.NewLicenseV3WithIDAndKey(l.ID, l.Key, licenseData)
if err != nil {
return nil, fmt.Errorf("failed to get licenses v3 schema : %v", err)
}
licenseV3Data = append(licenseV3Data, license)
}
return licenseV3Data, nil
}
// GetActiveLicense fetches the latest active license from DB.
// If the license is not present, expect a nil license and a nil error in the output.
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
@@ -79,6 +109,45 @@ func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel
return active, nil
}
func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error) {
var err error
licenses := []model.LicenseDB{}
query := "SELECT id,key,data FROM licenses_v3"
err = r.db.Select(&licenses, query)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
}
var active *model.LicenseV3
for _, l := range licenses {
var licenseData map[string]interface{}
err := json.Unmarshal([]byte(l.Data), &licenseData)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal data into licenseData : %v", err)
}
license, err := model.NewLicenseV3WithIDAndKey(l.ID, l.Key, licenseData)
if err != nil {
return nil, fmt.Errorf("failed to get licenses v3 schema : %v", err)
}
if active == nil &&
(license.ValidFrom != 0) &&
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
active = license
}
if active != nil &&
license.ValidFrom > active.ValidFrom &&
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
active = license
}
}
return active, nil
}
// InsertLicense inserts a new license in db
func (r *Repo) InsertLicense(ctx context.Context, l *model.License) error {
@@ -204,3 +273,59 @@ func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
}
return nil
}
// InsertLicenseV3 inserts a new license v3 in db
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) *model.ApiError {
query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`
// licsense is the entity of zeus so putting the entire license here without defining schema
licenseData, err := json.Marshal(l.Data)
if err != nil {
return &model.ApiError{Typ: basemodel.ErrorBadData, Err: err}
}
_, err = r.db.ExecContext(ctx,
query,
l.ID,
l.Key,
string(licenseData),
)
if err != nil {
if sqliteErr, ok := err.(sqlite3.Error); ok {
if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {
zap.L().Error("error in inserting license data: ", zap.Error(sqliteErr))
return &model.ApiError{Typ: model.ErrorConflict, Err: sqliteErr}
}
}
zap.L().Error("error in inserting license data: ", zap.Error(err))
return &model.ApiError{Typ: basemodel.ErrorExec, Err: err}
}
return nil
}
// UpdateLicenseV3 updates a new license v3 in db
func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {
// the key and id for the license can't change so only update the data here!
query := `UPDATE licenses_v3 SET data=$1 WHERE id=$2;`
license, err := json.Marshal(l.Data)
if err != nil {
return fmt.Errorf("insert license failed: license marshal error")
}
_, err = r.db.ExecContext(ctx,
query,
license,
l.ID,
)
if err != nil {
zap.L().Error("error in updating license data: ", zap.Error(err))
return fmt.Errorf("failed to update license in db: %v", err)
}
return nil
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"sync"
@@ -45,11 +46,12 @@ type Manager struct {
failedAttempts uint64
// keep track of active license and features
activeLicense *model.License
activeFeatures basemodel.FeatureSet
activeLicense *model.License
activeLicenseV3 *model.LicenseV3
activeFeatures basemodel.FeatureSet
}
func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*Manager, error) {
func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...basemodel.Feature) (*Manager, error) {
if LM != nil {
return LM, nil
}
@@ -65,7 +67,31 @@ func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*M
repo: &repo,
}
if err := m.start(features...); err != nil {
if useLicensesV3 {
// get active license from the db
active, err := m.repo.GetActiveLicense(context.Background())
if err != nil {
return m, err
}
// if we have an active license then need to fetch the complete details
if active != nil {
// fetch the new license structure from control plane
licenseV3, apiError := validate.ValidateLicenseV3(active.Key)
if apiError != nil {
return m, apiError
}
// insert the licenseV3 in sqlite db
apiError = m.repo.InsertLicenseV3(context.Background(), licenseV3)
// if the license already exists move ahead.
if apiError != nil && apiError.Typ != model.ErrorConflict {
return m, apiError
}
}
}
if err := m.start(useLicensesV3, features...); err != nil {
return m, err
}
LM = m
@@ -73,8 +99,14 @@ func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*M
}
// start loads active license in memory and initiates validator
func (lm *Manager) start(features ...basemodel.Feature) error {
err := lm.LoadActiveLicense(features...)
func (lm *Manager) start(useLicensesV3 bool, features ...basemodel.Feature) error {
var err error
if useLicensesV3 {
err = lm.LoadActiveLicenseV3(features...)
} else {
err = lm.LoadActiveLicense(features...)
}
return err
}
@@ -108,6 +140,31 @@ func (lm *Manager) SetActive(l *model.License, features ...basemodel.Feature) {
go lm.Validator(context.Background())
}
}
func (lm *Manager) SetActiveV3(l *model.LicenseV3, features ...basemodel.Feature) {
lm.mutex.Lock()
defer lm.mutex.Unlock()
if l == nil {
return
}
lm.activeLicenseV3 = l
lm.activeFeatures = append(l.Features, features...)
// set default features
setDefaultFeatures(lm)
err := lm.InitFeatures(lm.activeFeatures)
if err != nil {
zap.L().Panic("Couldn't activate features", zap.Error(err))
}
if !lm.validatorRunning {
// we want to make sure only one validator runs,
// we already have lock() so good to go
lm.validatorRunning = true
go lm.ValidatorV3(context.Background())
}
}
func setDefaultFeatures(lm *Manager) {
@@ -137,6 +194,28 @@ func (lm *Manager) LoadActiveLicense(features ...basemodel.Feature) error {
return nil
}
func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
active, err := lm.repo.GetActiveLicenseV3(context.Background())
if err != nil {
return err
}
if active != nil {
lm.SetActiveV3(active, features...)
} else {
zap.L().Info("No active license found, defaulting to basic plan")
// if no active license is found, we default to basic(free) plan with all default features
lm.activeFeatures = model.BasicPlan
setDefaultFeatures(lm)
err := lm.InitFeatures(lm.activeFeatures)
if err != nil {
zap.L().Error("Couldn't initialize features", zap.Error(err))
return err
}
}
return nil
}
func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *model.ApiError) {
licenses, err := lm.repo.GetLicenses(ctx)
@@ -163,6 +242,23 @@ func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, a
return
}
func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.LicenseV3, apiError *model.ApiError) {
licenses, err := lm.repo.GetLicensesV3(ctx)
if err != nil {
return nil, model.InternalError(err)
}
for _, l := range licenses {
if lm.activeLicenseV3 != nil && l.Key == lm.activeLicenseV3.Key {
l.IsCurrent = true
}
response = append(response, l)
}
return response, nil
}
// Validator validates license after an epoch of time
func (lm *Manager) Validator(ctx context.Context) {
defer close(lm.terminated)
@@ -187,6 +283,30 @@ func (lm *Manager) Validator(ctx context.Context) {
}
}
// Validator validates license after an epoch of time
func (lm *Manager) ValidatorV3(ctx context.Context) {
defer close(lm.terminated)
tick := time.NewTicker(validationFrequency)
defer tick.Stop()
lm.ValidateV3(ctx)
for {
select {
case <-lm.done:
return
default:
select {
case <-lm.done:
return
case <-tick.C:
lm.ValidateV3(ctx)
}
}
}
}
// Validate validates the current active license
func (lm *Manager) Validate(ctx context.Context) (reterr error) {
zap.L().Info("License validation started")
@@ -254,6 +374,54 @@ func (lm *Manager) Validate(ctx context.Context) (reterr error) {
return nil
}
// todo[vikrantgupta25]: check the comparison here between old and new license!
func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError {
license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key)
if apiError != nil {
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
return apiError
}
err := lm.repo.UpdateLicenseV3(ctx, license)
if err != nil {
return model.BadRequest(errors.Wrap(err, "failed to update the new license"))
}
lm.SetActiveV3(license)
return nil
}
func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
zap.L().Info("License validation started")
if lm.activeLicenseV3 == nil {
return nil
}
defer func() {
lm.mutex.Lock()
lm.lastValidated = time.Now().Unix()
if reterr != nil {
zap.L().Error("License validation completed with error", zap.Error(reterr))
atomic.AddUint64(&lm.failedAttempts, 1)
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
map[string]interface{}{"err": reterr.Error()}, "", true, false)
} else {
zap.L().Info("License validation completed with no errors")
}
lm.mutex.Unlock()
}()
err := lm.RefreshLicense(ctx)
if err != nil {
return err
}
return nil
}
// Activate activates a license key with signoz server
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *model.ApiError) {
defer func() {
@@ -298,6 +466,35 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
return l, nil
}
func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseResponse *model.LicenseV3, errResponse *model.ApiError) {
defer func() {
if errResponse != nil {
userEmail, err := auth.GetEmailFromJwt(ctx)
if err == nil {
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED,
map[string]interface{}{"err": errResponse.Err.Error()}, userEmail, true, false)
}
}
}()
license, apiError := validate.ValidateLicenseV3(licenseKey)
if apiError != nil {
zap.L().Error("failed to get the license", zap.Error(apiError.Err))
return nil, apiError
}
// insert the new license to the sqlite db
err := lm.repo.InsertLicenseV3(ctx, license)
if err != nil {
zap.L().Error("failed to activate license", zap.Error(err))
return nil, err
}
// license is valid, activate it
lm.SetActiveV3(license)
return license, nil
}
// CheckFeature will be internally used by backend routines
// for feature gating
func (lm *Manager) CheckFeature(featureKey string) error {

View File

@@ -48,5 +48,16 @@ func InitDB(db *sqlx.DB) error {
return fmt.Errorf("error in creating feature_status table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS licenses_v3 (
id TEXT PRIMARY KEY,
key TEXT NOT NULL UNIQUE,
data TEXT
);`
_, err = db.Exec(table_schema)
if err != nil {
return fmt.Errorf("error in creating licenses_v3 table: %s", err.Error())
}
return nil
}

View File

@@ -94,6 +94,7 @@ func main() {
var cluster string
var useLogsNewSchema bool
var useLicensesV3 bool
var cacheConfigPath, fluxInterval string
var enableQueryServiceLogOTLPExport bool
var preferSpanMetrics bool
@@ -104,6 +105,7 @@ func main() {
var gatewayUrl string
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
@@ -143,6 +145,7 @@ func main() {
Cluster: cluster,
GatewayUrl: gatewayUrl,
UseLogsNewSchema: useLogsNewSchema,
UseLicensesV3: useLicensesV3,
}
// Read the jwt secret key

View File

@@ -46,6 +46,13 @@ func BadRequest(err error) *ApiError {
}
}
func Unauthorized(err error) *ApiError {
return &ApiError{
Typ: basemodel.ErrorUnauthorized,
Err: err,
}
}
// BadRequestStr returns a ApiError object of bad request for string input
func BadRequestStr(s string) *ApiError {
return &ApiError{

View File

@@ -3,6 +3,8 @@ package model
import (
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/pkg/errors"
@@ -104,3 +106,144 @@ type SubscriptionServerResp struct {
Status string `json:"status"`
Data Licenses `json:"data"`
}
type Plan struct {
Name string `json:"name"`
}
type LicenseDB struct {
ID string `json:"id"`
Key string `json:"key"`
Data string `json:"data"`
}
type LicenseV3 struct {
ID string
Key string
Data map[string]interface{}
PlanName string
Features basemodel.FeatureSet
Status string
IsCurrent bool
ValidFrom int64
ValidUntil int64
}
func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
var zeroValue T
if val, ok := data[key]; ok {
if value, ok := val.(T); ok {
return value, nil
}
return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
}
return zeroValue, fmt.Errorf("%s key is missing", key)
}
func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
var features basemodel.FeatureSet
// extract id from data
licenseID, err := extractKeyFromMapStringInterface[string](data, "id")
if err != nil {
return nil, err
}
delete(data, "id")
// extract key from data
licenseKey, err := extractKeyFromMapStringInterface[string](data, "key")
if err != nil {
return nil, err
}
delete(data, "key")
// extract status from data
status, err := extractKeyFromMapStringInterface[string](data, "status")
if err != nil {
return nil, err
}
planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan")
if err != nil {
return nil, err
}
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
if err != nil {
return nil, err
}
// if license status is inactive then default it to basic
if status == LicenseStatusInactive {
planName = PlanNameBasic
}
featuresFromZeus := basemodel.FeatureSet{}
if _features, ok := data["features"]; ok {
featuresData, err := json.Marshal(_features)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal features data")
}
if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal features data")
}
}
switch planName {
case PlanNameTeams:
features = append(features, ProPlan...)
case PlanNameEnterprise:
features = append(features, EnterprisePlan...)
case PlanNameBasic:
features = append(features, BasicPlan...)
default:
features = append(features, BasicPlan...)
}
if len(featuresFromZeus) > 0 {
for _, feature := range featuresFromZeus {
exists := false
for i, existingFeature := range features {
if existingFeature.Name == feature.Name {
features[i] = feature // Replace existing feature
exists = true
break
}
}
if !exists {
features = append(features, feature) // Append if it doesn't exist
}
}
}
data["features"] = features
_validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from")
if err != nil {
_validFrom = 0
}
validFrom := int64(_validFrom)
_validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until")
if err != nil {
_validUntil = 0
}
validUntil := int64(_validUntil)
return &LicenseV3{
ID: licenseID,
Key: licenseKey,
Data: data,
PlanName: planName,
Features: features,
ValidFrom: validFrom,
ValidUntil: validUntil,
Status: status,
}, nil
}
func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) {
licenseDataWithIdAndKey := data
licenseDataWithIdAndKey["id"] = id
licenseDataWithIdAndKey["key"] = key
return NewLicenseV3(licenseDataWithIdAndKey)
}

View File

@@ -0,0 +1,170 @@
package model
import (
"encoding/json"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/query-service/model"
)
func TestNewLicenseV3(t *testing.T) {
testCases := []struct {
name string
data []byte
pass bool
expected *LicenseV3
error error
}{
{
name: "Error for missing license id",
data: []byte(`{}`),
pass: false,
error: errors.New("id key is missing"),
},
{
name: "Error for license id not being a valid string",
data: []byte(`{"id": 10}`),
pass: false,
error: errors.New("id key is not a valid string"),
},
{
name: "Error for missing license key",
data: []byte(`{"id":"does-not-matter"}`),
pass: false,
error: errors.New("key key is missing"),
},
{
name: "Error for invalid string license key",
data: []byte(`{"id":"does-not-matter","key":10}`),
pass: false,
error: errors.New("key key is not a valid string"),
},
{
name: "Error for missing license status",
data: []byte(`{"id":"does-not-matter", "key": "does-not-matter","category":"FREE"}`),
pass: false,
error: errors.New("status key is missing"),
},
{
name: "Error for invalid string license status",
data: []byte(`{"id":"does-not-matter","key": "does-not-matter", "category":"FREE", "status":10}`),
pass: false,
error: errors.New("status key is not a valid string"),
},
{
name: "Error for missing license plan",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
pass: false,
error: errors.New("plan key is missing"),
},
{
name: "Error for invalid json license plan",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
pass: false,
error: errors.New("plan key is not a valid map[string]interface {}"),
},
{
name: "Error for invalid license plan",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
pass: false,
error: errors.New("name key is missing"),
},
{
name: "Parse the entire license properly",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
pass: true,
expected: &LicenseV3{
ID: "does-not-matter",
Key: "does-not-matter-key",
Data: map[string]interface{}{
"plan": map[string]interface{}{
"name": "TEAMS",
},
"category": "FREE",
"status": "ACTIVE",
"valid_from": float64(1730899309),
"valid_until": float64(-1),
},
PlanName: PlanNameTeams,
ValidFrom: 1730899309,
ValidUntil: -1,
Status: "ACTIVE",
IsCurrent: false,
Features: model.FeatureSet{},
},
},
{
name: "Fallback to basic plan if license status is inactive",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
pass: true,
expected: &LicenseV3{
ID: "does-not-matter",
Key: "does-not-matter-key",
Data: map[string]interface{}{
"plan": map[string]interface{}{
"name": "TEAMS",
},
"category": "FREE",
"status": "INACTIVE",
"valid_from": float64(1730899309),
"valid_until": float64(-1),
},
PlanName: PlanNameBasic,
ValidFrom: 1730899309,
ValidUntil: -1,
Status: "INACTIVE",
IsCurrent: false,
Features: model.FeatureSet{},
},
},
{
name: "fallback states for validFrom and validUntil",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`),
pass: true,
expected: &LicenseV3{
ID: "does-not-matter",
Key: "does-not-matter-key",
Data: map[string]interface{}{
"plan": map[string]interface{}{
"name": "TEAMS",
},
"valid_from": 1234.456,
"valid_until": 5678.567,
"category": "FREE",
"status": "ACTIVE",
},
PlanName: PlanNameTeams,
ValidFrom: 1234,
ValidUntil: 5678,
Status: "ACTIVE",
IsCurrent: false,
Features: model.FeatureSet{},
},
},
}
for _, tc := range testCases {
var licensePayload map[string]interface{}
err := json.Unmarshal(tc.data, &licensePayload)
require.NoError(t, err)
license, err := NewLicenseV3(licensePayload)
if license != nil {
license.Features = make(model.FeatureSet, 0)
delete(license.Data, "features")
}
if tc.pass {
require.NoError(t, err)
require.NotNil(t, license)
assert.Equal(t, tc.expected, license)
} else {
require.Error(t, err)
assert.EqualError(t, err, tc.error.Error())
require.Nil(t, license)
}
}
}

View File

@@ -1,6 +1,7 @@
package model
import (
"go.signoz.io/signoz/pkg/query-service/constants"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
@@ -8,6 +9,17 @@ const SSO = "SSO"
const Basic = "BASIC_PLAN"
const Pro = "PRO_PLAN"
const Enterprise = "ENTERPRISE_PLAN"
var (
PlanNameEnterprise = "ENTERPRISE"
PlanNameTeams = "TEAMS"
PlanNameBasic = "BASIC"
)
var (
LicenseStatusInactive = "INACTIVE"
)
const DisableUpsell = "DISABLE_UPSELL"
const Onboarding = "ONBOARDING"
const ChatSupport = "CHAT_SUPPORT"
@@ -134,6 +146,13 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.HostsInfraMonitoring,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
@@ -249,6 +268,13 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.HostsInfraMonitoring,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var EnterprisePlan = basemodel.FeatureSet{
@@ -378,4 +404,11 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.HostsInfraMonitoring,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
}

View File

@@ -61,6 +61,11 @@ func NewAnomalyRule(
zap.L().Info("creating new AnomalyRule", zap.String("id", id), zap.Any("opts", opts))
if p.RuleCondition.CompareOp == baserules.ValueIsBelow {
target := -1 * *p.RuleCondition.Target
p.RuleCondition.Target = &target
}
baseRule, err := baserules.NewBaseRule(id, p, reader, opts...)
if err != nil {
return nil, err

View File

@@ -1,10 +1,15 @@
package rules
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
baserules "go.signoz.io/signoz/pkg/query-service/rules"
"go.signoz.io/signoz/pkg/query-service/utils/labels"
"go.uber.org/zap"
)
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
@@ -79,6 +84,106 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
return task, nil
}
// TestNotification prepares a dummy rule for given rule parameters and
// sends a test notification. returns alert count and error (if any)
func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.ApiError) {
ctx := context.Background()
if opts.Rule == nil {
return 0, basemodel.BadRequest(fmt.Errorf("rule is required"))
}
parsedRule := opts.Rule
var alertname = parsedRule.AlertName
if alertname == "" {
// alertname is not mandatory for testing, so picking
// a random string here
alertname = uuid.New().String()
}
// append name to indicate this is test alert
parsedRule.AlertName = fmt.Sprintf("%s%s", alertname, baserules.TestAlertPostFix)
var rule baserules.Rule
var err error
if parsedRule.RuleType == baserules.RuleTypeThreshold {
// add special labels for test alerts
parsedRule.Annotations[labels.AlertSummaryLabel] = fmt.Sprintf("The rule threshold is set to %.4f, and the observed metric value is {{$value}}.", *parsedRule.RuleCondition.Target)
parsedRule.Labels[labels.RuleSourceLabel] = ""
parsedRule.Labels[labels.AlertRuleIdLabel] = ""
// create a threshold rule
rule, err = baserules.NewThresholdRule(
alertname,
parsedRule,
opts.FF,
opts.Reader,
opts.UseLogsNewSchema,
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
)
if err != nil {
zap.L().Error("failed to prepare a new threshold rule for test", zap.String("name", rule.Name()), zap.Error(err))
return 0, basemodel.BadRequest(err)
}
} else if parsedRule.RuleType == baserules.RuleTypeProm {
// create promql rule
rule, err = baserules.NewPromRule(
alertname,
parsedRule,
opts.Logger,
opts.Reader,
opts.ManagerOpts.PqlEngine,
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
)
if err != nil {
zap.L().Error("failed to prepare a new promql rule for test", zap.String("name", rule.Name()), zap.Error(err))
return 0, basemodel.BadRequest(err)
}
} else if parsedRule.RuleType == baserules.RuleTypeAnomaly {
// create anomaly rule
rule, err = NewAnomalyRule(
alertname,
parsedRule,
opts.FF,
opts.Reader,
opts.Cache,
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
)
if err != nil {
zap.L().Error("failed to prepare a new anomaly rule for test", zap.String("name", rule.Name()), zap.Error(err))
return 0, basemodel.BadRequest(err)
}
} else {
return 0, basemodel.BadRequest(fmt.Errorf("failed to derive ruletype with given information"))
}
// set timestamp to current utc time
ts := time.Now().UTC()
count, err := rule.Eval(ctx, ts)
if err != nil {
zap.L().Error("evaluating rule failed", zap.String("rule", rule.Name()), zap.Error(err))
return 0, basemodel.InternalError(fmt.Errorf("rule evaluation failed"))
}
alertsFound, ok := count.(int)
if !ok {
return 0, basemodel.InternalError(fmt.Errorf("something went wrong"))
}
rule.SendAlerts(ctx, ts, 0, time.Duration(1*time.Minute), opts.NotifyFunc)
return alertsFound, nil
}
// newTask returns an appropriate group for
// rule type
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, ruleDB baserules.RuleDB) baserules.Task {

View File

@@ -34,7 +34,7 @@
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@grafana/data": "^9.5.2",
"@grafana/data": "^11.2.3",
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
"@monaco-editor/react": "^4.3.1",
@@ -42,7 +42,7 @@
"@radix-ui/react-tooltip": "1.0.7",
"@sentry/react": "7.102.1",
"@sentry/webpack-plugin": "2.16.0",
"@signozhq/design-tokens": "0.0.8",
"@signozhq/design-tokens": "1.1.4",
"@uiw/react-md-editor": "3.23.5",
"@visx/group": "3.3.0",
"@visx/shape": "3.5.0",
@@ -51,7 +51,7 @@
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
"axios": "1.7.4",
"axios": "1.7.7",
"babel-eslint": "^10.1.0",
"babel-jest": "^29.6.4",
"babel-loader": "9.1.3",
@@ -76,7 +76,7 @@
"fontfaceobserver": "2.3.0",
"history": "4.10.1",
"html-webpack-plugin": "5.5.0",
"http-proxy-middleware": "2.0.6",
"http-proxy-middleware": "2.0.7",
"i18next": "^21.6.12",
"i18next-browser-languagedetector": "^6.1.3",
"i18next-http-backend": "^1.3.2",
@@ -87,6 +87,8 @@
"lodash-es": "^4.17.21",
"lucide-react": "0.379.0",
"mini-css-extract-plugin": "2.4.5",
"overlayscrollbars": "^2.8.1",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "5.4.1",
"posthog-js": "1.160.3",
"rc-tween-one": "3.0.6",
@@ -107,11 +109,10 @@
"react-query": "3.39.3",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-router-dom-v5-compat": "6.27.0",
"react-syntax-highlighter": "15.5.0",
"react-use": "^17.3.2",
"react-virtuoso": "4.0.3",
"overlayscrollbars-react": "^0.5.6",
"overlayscrollbars": "^2.8.1",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"rehype-raw": "7.0.0",
@@ -123,10 +124,10 @@
"ts-node": "^10.2.1",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"typescript": "^4.0.5",
"uplot": "1.6.26",
"uplot": "1.6.31",
"uuid": "^8.3.2",
"web-vitals": "^0.2.4",
"webpack": "5.88.2",
"webpack": "5.94.0",
"webpack-dev-server": "^4.15.1",
"webpack-retry-chunk-load-plugin": "3.1.1",
"xstate": "^4.31.0"
@@ -185,6 +186,7 @@
"@types/webpack-dev-server": "^4.7.2",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vvo/tzdb": "6.149.0",
"autoprefixer": "10.4.19",
"babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "9.0.0",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,30 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
<rect y="0.25" width="17.5" height="17.5" rx="3.5" fill="url(#paint0_radial_274_16499)"/>
<g filter="url(#filter0_di_274_16499)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.92383 8.98972L1.92392 8.98924L1.92413 8.98821L1.92462 8.98579L1.92593 8.97961C1.92695 8.9749 1.92827 8.96901 1.92993 8.96197C1.93325 8.94788 1.93793 8.9292 1.94429 8.9062C1.95702 8.8602 1.97646 8.79692 2.00515 8.71856C2.06254 8.56178 2.15683 8.34494 2.30807 8.08559C2.6111 7.56595 3.13965 6.88108 4.04917 6.16856C4.96329 5.45246 5.85086 5.06731 6.51337 4.861C6.84464 4.75784 7.12019 4.69922 7.3156 4.66621C7.41335 4.6497 7.49117 4.63958 7.54605 4.63349C7.5735 4.63045 7.59522 4.62841 7.61085 4.62709C7.61866 4.62643 7.62495 4.62595 7.62966 4.62561L7.63556 4.6252L7.63761 4.62507L7.63876 4.625C7.63876 4.625 7.63906 4.62498 7.66199 5.01301C7.68491 5.40105 7.68518 5.40103 7.68518 5.40103L7.68483 5.40106C7.68326 5.40117 7.68017 5.40141 7.67559 5.40179C7.66643 5.40257 7.65136 5.40396 7.63073 5.40625C7.58948 5.41082 7.52607 5.41898 7.44345 5.43293C7.30197 5.45683 7.10452 5.49768 6.86568 5.56652C6.85832 5.56899 6.85091 5.57151 6.84345 5.57406C6.36305 5.73839 5.67696 6.05963 4.87486 6.68728C4.07477 7.31337 3.62509 7.90371 3.37636 8.33106C3.25185 8.54497 3.1773 8.71874 3.13432 8.83664C3.11283 8.89561 3.09922 8.94064 3.09121 8.96977C3.08746 8.98339 3.08494 8.99353 3.08341 9C3.08494 9.00647 3.08746 9.01661 3.09121 9.03023C3.09922 9.05936 3.11283 9.10439 3.13432 9.16336C3.1773 9.28126 3.25185 9.45503 3.37636 9.66894C3.62509 10.0963 4.07477 10.6866 4.87486 11.3127C5.67696 11.9404 6.36305 12.2616 6.84345 12.4259C6.85091 12.4285 6.85832 12.431 6.86568 12.4335C7.10452 12.5023 7.30197 12.5432 7.44345 12.5671C7.52607 12.581 7.58948 12.5892 7.63073 12.5938C7.65136 12.596 7.66643 12.5974 7.67559 12.5982C7.68017 12.5986 7.68326 12.5988 7.68483 12.5989L7.68518 12.599C7.68518 12.599 7.68491 12.599 7.66199 12.987C7.63906 13.375 7.63876 13.375 7.63876 13.375L7.63841 13.375L7.63761 13.3749L7.63556 13.3748L7.62966 13.3744C7.62495 13.3741 7.61866 13.3736 7.61085 13.3729C7.59522 13.3716 7.5735 13.3696 7.54605 13.3665C7.49117 13.3604 7.41335 13.3503 7.3156 13.3338C7.12019 13.3008 6.84464 13.2422 6.51337 13.139C5.85086 12.9327 4.96329 12.5475 4.04917 11.8314C3.13965 11.1189 2.6111 10.434 2.30807 9.91441C2.15683 9.65506 2.06254 9.43822 2.00515 9.28144C1.97646 9.20308 1.95702 9.1398 1.94429 9.0938C1.93793 9.0708 1.93325 9.05212 1.92993 9.03803C1.92827 9.03099 1.92695 9.0251 1.92593 9.02039L1.92462 9.01421L1.92413 9.01179L1.92392 9.01076L1.92383 9.01028V8.98972Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5738 8.98972L15.5737 8.98924L15.5735 8.98821L15.573 8.98579L15.5717 8.97961C15.5707 8.9749 15.5694 8.96901 15.5677 8.96197C15.5644 8.94788 15.5597 8.9292 15.5534 8.9062C15.5406 8.8602 15.5212 8.79692 15.4925 8.71856C15.4351 8.56178 15.3408 8.34494 15.1896 8.08559C14.8866 7.56595 14.358 6.88108 13.4485 6.16856C12.5344 5.45246 11.6468 5.06731 10.9843 4.861C10.653 4.75784 10.3775 4.69922 10.1821 4.66621C10.0843 4.6497 10.0065 4.63958 9.9516 4.63349C9.92416 4.63045 9.90243 4.62841 9.88681 4.62709C9.879 4.62643 9.87271 4.62595 9.86799 4.62561L9.8621 4.6252L9.86004 4.62507L9.8589 4.625C9.8589 4.625 9.85859 4.62498 9.83567 5.01301C9.81275 5.40105 9.81248 5.40103 9.81248 5.40103L9.81282 5.40106C9.81439 5.40117 9.81749 5.40141 9.82207 5.40179C9.83122 5.40257 9.8463 5.40396 9.86692 5.40625C9.90818 5.41082 9.97158 5.41898 10.0542 5.43293C10.1957 5.45683 10.3931 5.49768 10.632 5.56652C10.6393 5.56899 10.6467 5.57151 10.6542 5.57406C11.1346 5.73839 11.8207 6.05963 12.6228 6.68728C13.4229 7.31337 13.8726 7.90371 14.1213 8.33106C14.2458 8.54497 14.3204 8.71874 14.3633 8.83664C14.3848 8.89561 14.3984 8.94064 14.4064 8.96977C14.4102 8.98339 14.4127 8.99353 14.4142 9C14.4127 9.00647 14.4102 9.01661 14.4064 9.03023C14.3984 9.05936 14.3848 9.10439 14.3633 9.16336C14.3204 9.28126 14.2458 9.45503 14.1213 9.66894C13.8726 10.0963 13.4229 10.6866 12.6228 11.3127C11.8207 11.9404 11.1346 12.2616 10.6542 12.4259C10.6467 12.4285 10.6393 12.431 10.632 12.4335C10.3931 12.5023 10.1957 12.5432 10.0542 12.5671C9.97158 12.581 9.90818 12.5892 9.86692 12.5938C9.8463 12.596 9.83122 12.5974 9.82207 12.5982C9.81749 12.5986 9.81439 12.5988 9.81282 12.5989L9.81248 12.599C9.81248 12.599 9.81275 12.599 9.83567 12.987C9.85859 13.375 9.8589 13.375 9.8589 13.375L9.85924 13.375L9.86004 13.3749L9.8621 13.3748L9.86799 13.3744C9.87271 13.3741 9.879 13.3736 9.88681 13.3729C9.90243 13.3716 9.92416 13.3696 9.9516 13.3665C10.0065 13.3604 10.0843 13.3503 10.1821 13.3338C10.3775 13.3008 10.653 13.2422 10.9843 13.139C11.6468 12.9327 12.5344 12.5475 13.4485 11.8314C14.358 11.1189 14.8866 10.434 15.1896 9.91441C15.3408 9.65506 15.4351 9.43822 15.4925 9.28144C15.5212 9.20308 15.5406 9.1398 15.5534 9.0938C15.5597 9.0708 15.5644 9.05212 15.5677 9.03803C15.5694 9.03099 15.5707 9.0251 15.5717 9.02039L15.573 9.01421L15.5735 9.01179L15.5737 9.01076L15.5738 9.01028V8.98972Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9587 8.99965C11.9587 10.7227 10.5719 12.1194 8.86125 12.1194C7.15057 12.1194 5.76379 10.7227 5.76379 8.99965C5.76379 7.27663 7.15057 5.87985 8.86125 5.87985C10.5719 5.87985 11.9587 7.27663 11.9587 8.99965ZM8.08689 7.24476C8.08689 7.24476 7.7147 7.68363 7.60291 8.02471C7.53083 8.24463 7.50611 8.60967 7.50611 8.60967H6.53816C6.53816 8.60967 6.53816 8.31719 6.73175 7.82972C6.92534 7.34225 7.11893 7.24476 7.11893 7.24476H8.08689ZM7.60291 9.97458C7.7147 10.3157 8.08689 10.7545 8.08689 10.7545H7.11893C7.11893 10.7545 6.92534 10.657 6.73175 10.1696C6.53816 9.6821 6.53816 9.38962 6.53816 9.38962H7.50611C7.50611 9.38962 7.53083 9.75466 7.60291 9.97458Z" fill="white"/>
</g>
<defs>
<filter id="filter0_di_274_16499" x="0.873828" y="4.275" width="15.7504" height="10.85" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.7"/>
<feGaussianBlur stdDeviation="0.525"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.368384 0 0 0 0 0.0623777 0 0 0 0 0.0623777 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_274_16499"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_274_16499" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.7"/>
<feGaussianBlur stdDeviation="0.525"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_274_16499"/>
</filter>
<radialGradient id="paint0_radial_274_16499" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.75 9) rotate(45.6903) scale(10.2715)">
<stop offset="0.329632" stop-color="#FF5E19"/>
<stop offset="1" stop-color="#FF2929"/>
</radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -1,72 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="17" viewBox="0 0 18 17" fill="none">
<g clip-path="url(#clip0_349_4942)">
<path d="M8.88797 0.741002C9.94502 0.741002 10.9731 0.941804 11.9399 1.34027C12.8745 1.72619 13.7125 2.27526 14.4311 2.97494C15.1498 3.6746 15.717 4.4935 16.1102 5.40024C16.5195 6.34152 16.7257 7.33927 16.7257 8.37151C16.7257 9.17473 16.6 9.95907 16.3486 10.7152L14.7566 9.90887C14.8952 9.41001 14.9628 8.89232 14.9628 8.37151C14.9628 7.57144 14.8018 6.79645 14.4858 6.06853C14.1797 5.36575 13.7415 4.73194 13.1839 4.18915C12.6264 3.64636 11.9754 3.21966 11.2535 2.92159C10.5058 2.61412 9.70977 2.45724 8.88797 2.45724C8.06614 2.45724 7.27014 2.61412 6.52245 2.92159C5.80055 3.21966 5.14954 3.64636 4.59201 4.18915C4.03448 4.73194 3.59619 5.36575 3.29002 6.06853C2.97419 6.79645 2.81306 7.57144 2.81306 8.37151C2.81306 8.89547 2.88394 9.41313 3.0193 9.91202L1.42727 10.7184C1.17589 9.96226 1.0502 9.17473 1.0502 8.37151C1.0502 7.34239 1.25646 6.34152 1.66575 5.40024C2.06215 4.49035 2.62613 3.6746 3.3448 2.97494C4.06348 2.27526 4.90462 1.72305 5.83601 1.34027C6.80284 0.941804 7.83088 0.741002 8.88797 0.741002ZM8.88797 0.527649C4.43731 0.527649 0.831055 4.03855 0.831055 8.37151C0.831055 9.3002 0.998638 10.1913 1.30158 11.0196L3.27713 10.0219C3.11277 9.49165 3.02899 8.9394 3.02899 8.37466C3.02899 7.60278 3.18367 6.8561 3.48984 6.1564C3.78632 5.47869 4.20851 4.86689 4.74671 4.3429C5.2849 3.81894 5.91336 3.40791 6.60946 3.11926C7.33136 2.82119 8.09836 2.67059 8.88797 2.67059C9.67755 2.67059 10.4478 2.82119 11.1664 3.11926C11.8626 3.40791 12.491 3.81894 13.0292 4.3429C13.5674 4.86689 13.9895 5.47869 14.2861 6.1564C14.5922 6.85921 14.7469 7.60593 14.7469 8.37466C14.7469 8.9394 14.6632 9.49165 14.4988 10.0219L16.4743 11.0196C16.7805 10.1913 16.9448 9.3002 16.9448 8.37151C16.9448 4.03855 13.3386 0.527649 8.88797 0.527649Z" fill="#F5F5F5"/>
<path d="M8.88813 16.2152C12.3848 16.2152 15.3595 14.0471 16.4745 11.0162L14.499 10.0185C14.4378 10.213 14.3701 10.4044 14.2862 10.5895C13.9898 11.2673 13.5676 11.8791 13.0294 12.4031C12.4912 12.927 11.8627 13.338 11.1666 13.6267C10.4447 13.9248 9.67771 14.0754 8.88813 14.0754C8.09853 14.0754 7.32832 13.9248 6.60963 13.6267C5.91352 13.338 5.2851 12.927 4.74687 12.4031C4.20868 11.8791 3.7865 11.2673 3.49002 10.5895C3.40945 10.4013 3.33854 10.213 3.2773 10.0185L1.30176 11.0162C2.41683 14.0471 5.39142 16.2152 8.88813 16.2152Z" fill="#414141"/>
<path d="M5.0172 7.46759H4.78516L5.68755 5.43448H5.89383L6.78973 7.46759H6.55447L6.32564 6.93424H5.24603L5.0172 7.46759ZM5.32338 6.74279H6.24508L5.78423 5.6635L5.32338 6.74279Z" fill="#F5F5F5"/>
<path d="M7.05078 7.46762V5.43451H7.26346V7.46762H7.05078Z" fill="#F5F5F5"/>
<path d="M9.5196 7.13834C9.48417 7.18861 9.44226 7.23878 9.3907 7.28583C9.33908 7.33288 9.28113 7.3737 9.2167 7.40818C9.15217 7.4427 9.07809 7.47095 8.9975 7.49293C8.91691 7.51173 8.82996 7.5243 8.73648 7.5243C8.5786 7.5243 8.43351 7.49605 8.30143 7.4427C8.16604 7.38938 8.05326 7.31408 7.95657 7.21994C7.85986 7.12578 7.78578 7.01287 7.73095 6.88107C7.67622 6.75248 7.65039 6.60812 7.65039 6.4544C7.65039 6.30064 7.6794 6.15631 7.73416 6.02769C7.78899 5.89904 7.86634 5.7861 7.963 5.68881C8.05968 5.59471 8.17574 5.51938 8.30782 5.46606C8.44 5.41274 8.585 5.38449 8.73969 5.38449C8.81704 5.38449 8.89118 5.39076 8.96525 5.40644C9.03942 5.42213 9.10707 5.44411 9.17478 5.47233C9.23922 5.50058 9.29726 5.53509 9.35199 5.57273C9.40361 5.61351 9.44868 5.65745 9.48417 5.70765L9.30686 5.83315C9.28435 5.80178 9.25534 5.76727 9.21991 5.7359C9.18442 5.70453 9.13935 5.67313 9.09094 5.65118C9.04263 5.62608 8.98783 5.60725 8.92982 5.59156C8.87181 5.57588 8.81059 5.56958 8.74291 5.56958C8.60755 5.56958 8.48513 5.59471 8.37875 5.64176C8.27242 5.68885 8.18216 5.7547 8.10481 5.83627C8.03065 5.91787 7.9727 6.012 7.93399 6.11867C7.89535 6.22534 7.87598 6.33828 7.87598 6.45754C7.87598 6.57675 7.89535 6.68972 7.9372 6.79638C7.97912 6.90309 8.03392 6.99719 8.1113 7.07873C8.18537 7.16036 8.2756 7.22309 8.38517 7.27329C8.49156 7.32031 8.614 7.34545 8.74612 7.34545C8.86539 7.34545 8.98138 7.32031 9.08773 7.27329C9.1973 7.22621 9.28756 7.14779 9.35848 7.04424L9.5196 7.13834Z" fill="#F5F5F5"/>
<path d="M9.80664 5.43451H10.3964C10.5027 5.43451 10.5994 5.44704 10.6864 5.46903C10.7735 5.49098 10.8476 5.52549 10.9089 5.56943C10.97 5.61336 11.0184 5.66983 11.0506 5.73886C11.0829 5.80787 11.1022 5.88629 11.1022 5.9773C11.1022 6.06829 11.086 6.14986 11.0506 6.21574C11.0151 6.28478 10.9668 6.34124 10.9056 6.38518C10.8443 6.42911 10.7703 6.46363 10.68 6.48873C10.5929 6.51068 10.4963 6.52321 10.3899 6.52321H10.0193V7.46762H9.80664V5.43451ZM10.3803 6.34754C10.5478 6.34754 10.6735 6.31614 10.7606 6.25341C10.8476 6.19064 10.8895 6.09966 10.8895 5.9773C10.8895 5.8518 10.8443 5.75767 10.7541 5.7012C10.6639 5.64158 10.5382 5.61336 10.377 5.61336H10.0161V6.34754H10.3803Z" fill="#F5F5F5"/>
<path d="M11.2183 7.46762H10.9863L11.8887 5.43451H12.0949L12.9909 7.46762H12.7556L12.5268 6.93427H11.4472L11.2183 7.46762ZM11.5246 6.74282H12.4462L11.9853 5.66353L11.5246 6.74282Z" fill="#F5F5F5"/>
<path d="M5.86827 9.80213C5.82638 9.74878 5.76837 9.70491 5.69748 9.67345C5.62658 9.64208 5.5589 9.6264 5.49444 9.6264C5.46222 9.6264 5.42679 9.62958 5.39133 9.63585C5.35586 9.64208 5.32686 9.65153 5.29785 9.67033C5.26885 9.68602 5.2463 9.70803 5.22696 9.7331C5.20762 9.7582 5.19796 9.79272 5.19796 9.8335C5.19796 9.86799 5.20441 9.89624 5.2205 9.91825C5.23663 9.94017 5.25597 9.96218 5.28497 9.97787C5.31398 9.99355 5.3462 10.0124 5.38811 10.0249C5.42679 10.0406 5.47189 10.0532 5.52023 10.0689C5.59112 10.0908 5.66526 10.1159 5.74261 10.1442C5.81996 10.1723 5.88764 10.21 5.95207 10.2539C6.01654 10.301 6.06809 10.3575 6.10998 10.4234C6.15187 10.4923 6.17124 10.5771 6.17124 10.6775C6.17124 10.7936 6.14866 10.894 6.10356 10.9819C6.05843 11.0665 6.00041 11.1387 5.92631 11.1921C5.85217 11.2485 5.76516 11.2893 5.66847 11.3144C5.57182 11.3427 5.47189 11.3552 5.37199 11.3552C5.22375 11.3552 5.07872 11.3301 4.9369 11.2799C4.79833 11.2297 4.68231 11.1575 4.58887 11.0634L4.92081 10.7372C4.97237 10.7999 5.04005 10.8501 5.12382 10.8909C5.20762 10.9316 5.29143 10.9536 5.37199 10.9536C5.41066 10.9536 5.44609 10.9505 5.48156 10.941C5.51702 10.9348 5.54924 10.9222 5.57503 10.9034C5.60079 10.8878 5.62337 10.8626 5.63947 10.8344C5.65559 10.8061 5.66205 10.7716 5.66205 10.7308C5.66205 10.6932 5.65238 10.6586 5.63301 10.6336C5.6137 10.6053 5.58467 10.5834 5.54924 10.5614C5.51377 10.5395 5.46868 10.5206 5.41388 10.5018C5.35908 10.483 5.29785 10.4642 5.23017 10.4422C5.16249 10.4202 5.09806 10.3951 5.03359 10.3669C4.96915 10.3386 4.91114 10.301 4.8628 10.2571C4.81121 10.2132 4.77257 10.1567 4.74032 10.0939C4.71131 10.0312 4.69519 9.95274 4.69519 9.85863C4.69519 9.74563 4.71777 9.64841 4.76611 9.56999C4.81446 9.48836 4.87568 9.42251 4.95303 9.37234C5.03038 9.32208 5.11739 9.28132 5.21405 9.25931C5.31073 9.23421 5.40742 9.22482 5.50735 9.22482C5.62658 9.22482 5.74582 9.24677 5.86827 9.28756C5.99078 9.32841 6.10031 9.39111 6.19057 9.4758L5.86827 9.80213Z" fill="#F5F5F5"/>
<path d="M8.61766 10.2823C8.61766 10.4422 8.58865 10.5897 8.53064 10.7246C8.47266 10.8564 8.39531 10.9725 8.29217 11.0666C8.1923 11.1608 8.06982 11.2361 7.93122 11.2863C7.79265 11.3364 7.64113 11.3647 7.47683 11.3647C7.31243 11.3647 7.16422 11.3395 7.02237 11.2863C6.8838 11.2361 6.76457 11.1608 6.66142 11.0666C6.56153 10.9725 6.48097 10.8564 6.42295 10.7246C6.36494 10.5928 6.33594 10.4453 6.33594 10.2823C6.33594 10.1191 6.36494 9.97162 6.42295 9.83988C6.48097 9.70808 6.55831 9.59826 6.66142 9.50409C6.76135 9.41311 6.8838 9.34099 7.02237 9.29076C7.16095 9.24055 7.31243 9.21545 7.47683 9.21545C7.64113 9.21545 7.79265 9.24055 7.93122 9.29076C8.06982 9.34099 8.1923 9.40999 8.29217 9.50409C8.39204 9.59508 8.47266 9.70808 8.53064 9.83988C8.58865 9.97162 8.61766 10.1191 8.61766 10.2823ZM8.07622 10.2823C8.07622 10.1944 8.06012 10.1128 8.03112 10.0344C8.00211 9.95902 7.96022 9.89317 7.90864 9.83667C7.85708 9.78338 7.79265 9.73945 7.71851 9.70808C7.64437 9.67668 7.56381 9.66103 7.47355 9.66103C7.38335 9.66103 7.30604 9.67668 7.2319 9.70808C7.15777 9.73945 7.09651 9.78338 7.04174 9.83667C6.99016 9.89005 6.94827 9.95593 6.91926 10.0344C6.89026 10.1097 6.87738 10.1944 6.87738 10.2823C6.87738 10.3732 6.89347 10.4579 6.92248 10.5364C6.95151 10.6148 6.9934 10.6807 7.04495 10.7372C7.09651 10.7936 7.16095 10.8375 7.2319 10.8689C7.30604 10.9003 7.38657 10.9161 7.47355 10.9161C7.5606 10.9161 7.64116 10.9003 7.7153 10.8689C7.78943 10.8375 7.85066 10.7936 7.90543 10.7372C7.95698 10.6807 7.9989 10.6148 8.0279 10.5364C8.06012 10.4579 8.07622 10.3732 8.07622 10.2823Z" fill="#F5F5F5"/>
<path d="M10.3932 11.2704C10.2578 11.33 10.0999 11.3614 9.92265 11.3614C9.76146 11.3614 9.61326 11.3362 9.47468 11.283C9.33929 11.2327 9.22012 11.1574 9.12016 11.0633C9.02026 10.9692 8.93967 10.8562 8.8849 10.7244C8.82689 10.5926 8.79785 10.4484 8.79785 10.2883C8.79785 10.1251 8.82689 9.97773 8.8849 9.84593C8.94294 9.71412 9.02347 9.60119 9.12658 9.50708C9.22972 9.41292 9.34899 9.34395 9.48756 9.29375C9.62617 9.24351 9.77438 9.21844 9.93229 9.21844C10.0806 9.21844 10.2256 9.24351 10.3674 9.29375C10.5092 9.34395 10.6252 9.41922 10.7155 9.51647L10.3706 9.84904C10.3222 9.78631 10.261 9.73926 10.1836 9.70789C10.1063 9.67652 10.0289 9.66081 9.94838 9.66081C9.86139 9.66081 9.77759 9.67652 9.70352 9.70789C9.62935 9.73926 9.56494 9.78319 9.51333 9.83969C9.46177 9.89613 9.41985 9.96204 9.39085 10.0373C9.3619 10.1126 9.34578 10.1973 9.34578 10.2852C9.34578 10.3762 9.3619 10.4609 9.39085 10.5394C9.41985 10.6147 9.46177 10.6805 9.51333 10.737C9.56494 10.7903 9.62935 10.8342 9.70024 10.8656C9.77438 10.897 9.85173 10.9127 9.93877 10.9127C10.0386 10.9127 10.1257 10.8938 10.1998 10.8562C10.2739 10.8185 10.3352 10.7684 10.3803 10.7056L10.7316 11.0288C10.6413 11.1292 10.5286 11.2108 10.3932 11.2704Z" fill="#F5F5F5"/>
<path d="M11.6731 10.9035L12.4884 10.1881C12.54 10.141 12.5819 10.0908 12.6109 10.0407C12.6431 9.99041 12.6593 9.93709 12.6593 9.87438C12.6593 9.8022 12.6366 9.74258 12.5883 9.69865C12.54 9.65472 12.4787 9.63276 12.3982 9.63276C12.3143 9.63276 12.2467 9.66101 12.1952 9.71745C12.1435 9.77395 12.1113 9.84922 12.1017 9.94024L11.6279 9.87747C11.6408 9.77395 11.6698 9.68293 11.7149 9.6014C11.7601 9.5198 11.8181 9.45079 11.889 9.39429C11.9599 9.33779 12.0405 9.29389 12.1339 9.2657C12.2242 9.23431 12.3208 9.22177 12.424 9.22177C12.5206 9.22177 12.6141 9.23431 12.7044 9.25937C12.7946 9.28451 12.8751 9.32214 12.946 9.37549C13.017 9.42566 13.0749 9.49161 13.1169 9.57003C13.1588 9.64845 13.1813 9.73943 13.1813 9.84613C13.1813 9.91832 13.1717 9.98417 13.1523 10.0438C13.1329 10.1034 13.1039 10.1567 13.0717 10.207C13.0363 10.2571 12.9976 10.3042 12.9525 10.345C12.9074 10.3889 12.8623 10.4296 12.814 10.4736L12.3305 10.8941H13.1845V11.3145H11.6731V10.9035Z" fill="#F5F5F5"/>
<path d="M12.9903 8.32452H4.78516V8.41233H12.9903V8.32452Z" fill="#F5F5F5"/>
<path d="M2.4424 8.12685L2.43596 8.2053L1.7334 7.84445L1.73984 7.77541L2.49396 7.52759L2.48752 7.60912L2.29093 7.67192L2.26193 8.03898L2.4424 8.12685ZM2.19103 8.00443L2.21681 7.69073L1.81719 7.81623L2.19103 8.00443Z" fill="#F5F5F5"/>
<path d="M2.52356 7.37037L2.51067 7.44249L1.79199 7.33582L1.80488 7.2637L2.52356 7.37037Z" fill="#F5F5F5"/>
<path d="M2.57107 6.59571C2.58718 6.6114 2.60008 6.63023 2.61297 6.64906C2.62586 6.66783 2.63553 6.69297 2.64197 6.71492C2.64842 6.74002 2.65165 6.76515 2.65487 6.7934C2.65487 6.82159 2.65165 6.84984 2.64519 6.8812C2.63231 6.93456 2.61297 6.97849 2.58396 7.0193C2.55496 7.06006 2.51951 7.09143 2.47761 7.11653C2.43572 7.14166 2.3906 7.15737 2.34226 7.16361C2.29392 7.16984 2.23913 7.16984 2.18757 7.15737C2.136 7.14478 2.08443 7.12597 2.04576 7.09454C2.00387 7.06636 1.97164 7.03181 1.94585 6.99105C1.92008 6.95024 1.90396 6.9063 1.89429 6.85928C1.88463 6.8122 1.88785 6.76203 1.90074 6.70865C1.90719 6.68355 1.91685 6.65848 1.92652 6.63647C1.93941 6.61452 1.9523 6.59257 1.9652 6.57373C1.98131 6.55493 1.99742 6.5361 2.01354 6.52356C2.03287 6.50785 2.05221 6.49846 2.07154 6.48901L2.10055 6.55805C2.08766 6.5612 2.07477 6.57061 2.05866 6.58003C2.04576 6.58942 2.02965 6.60198 2.01676 6.61452C2.00387 6.63023 1.9942 6.64592 1.98131 6.6616C1.97164 6.6804 1.9652 6.69923 1.95875 6.72122C1.94908 6.76515 1.94585 6.80908 1.95553 6.84672C1.9652 6.8875 1.97808 6.92199 2.00065 6.95339C2.0232 6.98476 2.05221 7.00986 2.08443 7.03181C2.11989 7.05379 2.15534 7.06951 2.19723 7.07886C2.23913 7.08828 2.28103 7.09142 2.3197 7.08519C2.36159 7.07886 2.39704 7.06951 2.4325 7.05067C2.46795 7.03181 2.49695 7.00674 2.52273 6.97537C2.54851 6.944 2.56463 6.9063 2.57752 6.86237C2.58718 6.82159 2.58718 6.78395 2.58074 6.74317C2.57107 6.70241 2.55173 6.66784 2.52273 6.63647L2.57107 6.59571Z" fill="#F5F5F5"/>
<path d="M2.02637 6.31033L2.09404 6.11891C2.10693 6.08442 2.11983 6.05617 2.13916 6.02795C2.15527 6.00282 2.17461 5.98087 2.19717 5.96834C2.21973 5.95262 2.24551 5.94323 2.2713 5.94009C2.29707 5.93694 2.32608 5.94009 2.35831 5.9495C2.39054 5.95892 2.41632 5.9746 2.43565 5.9934C2.45499 6.01227 2.46788 6.03422 2.47755 6.05932C2.48722 6.08442 2.49045 6.11267 2.48722 6.14401C2.48399 6.17541 2.47755 6.20677 2.46466 6.24129L2.42276 6.36053L2.74504 6.4672L2.72248 6.5362L2.02637 6.31033ZM2.36154 6.34484L2.40343 6.22873C2.42276 6.17541 2.42598 6.13147 2.4131 6.09384C2.40021 6.05932 2.37442 6.03422 2.33253 6.02165C2.29063 6.00597 2.25196 6.01227 2.22295 6.03422C2.19395 6.05617 2.16817 6.09384 2.15205 6.14716L2.11015 6.26327L2.36154 6.34484Z" fill="#F5F5F5"/>
<path d="M2.87737 6.08738L2.84513 6.15954L2.30371 5.59167L2.33271 5.52576L3.12874 5.53202L3.09651 5.60421L2.88703 5.60106L2.73556 5.93678L2.87737 6.08738ZM2.68399 5.88973L2.81291 5.60421L2.39394 5.59479L2.68399 5.88973Z" fill="#F5F5F5"/>
<path d="M2.96127 4.63788C2.9355 4.641 2.90972 4.6473 2.88392 4.65983C2.85814 4.67237 2.83881 4.69435 2.81946 4.7226C2.8098 4.73828 2.80337 4.75397 2.79692 4.7728C2.79047 4.7916 2.79047 4.81044 2.79047 4.82612C2.79368 4.84495 2.79692 4.86064 2.80658 4.87947C2.81625 4.89515 2.83236 4.91084 2.85171 4.92337C2.87104 4.93594 2.89037 4.94221 2.90648 4.94221C2.9226 4.94221 2.94194 4.93909 2.95806 4.93279C2.97417 4.92652 2.99029 4.9171 3.0064 4.90457C3.02249 4.892 3.03863 4.87947 3.05474 4.8669C3.07407 4.85122 3.09663 4.83242 3.1192 4.8167C3.14174 4.80102 3.1643 4.78848 3.18687 4.77907C3.20943 4.76965 3.23521 4.7665 3.26422 4.7665C3.29 4.7665 3.31899 4.77907 3.35122 4.79787C3.38345 4.8167 3.40601 4.83865 3.42213 4.86375C3.43824 4.88889 3.4479 4.91399 3.4479 4.94221C3.45114 4.97046 3.4479 4.99556 3.44145 5.02381C3.43502 5.05202 3.42212 5.07712 3.40601 5.10222C3.39311 5.12106 3.38023 5.13674 3.36412 5.15243C3.34801 5.16811 3.33189 5.18379 3.31256 5.19324C3.29321 5.20578 3.27389 5.21516 3.25454 5.22146C3.23521 5.22773 3.21264 5.23403 3.19008 5.23403L3.18365 5.15558C3.21264 5.15558 3.24487 5.14931 3.27389 5.13359C3.30288 5.11791 3.32868 5.09596 3.34801 5.06459C3.35767 5.04887 3.36412 5.03319 3.37057 5.01439C3.377 4.99556 3.377 4.97672 3.37378 4.95792C3.37057 4.93909 3.36733 4.92025 3.35446 4.90457C3.34477 4.88889 3.32868 4.8732 3.30933 4.86064C3.28676 4.8481 3.26744 4.8418 3.24809 4.8418C3.22876 4.8418 3.20943 4.84495 3.19008 4.85437C3.17075 4.86375 3.15464 4.8732 3.13531 4.88574C3.1192 4.8983 3.09985 4.91399 3.08052 4.92967C3.06117 4.94535 3.04184 4.96104 3.02249 4.97672C3.00316 4.99241 2.98062 5.00182 2.95806 5.00809C2.93549 5.01439 2.90972 5.01751 2.88715 5.01751C2.86138 5.01439 2.83558 5.00497 2.80658 4.98926C2.77757 4.97046 2.75503 4.94847 2.7389 4.92652C2.72601 4.90142 2.71635 4.87632 2.71312 4.8481C2.7099 4.81985 2.71312 4.79475 2.72279 4.7665C2.72923 4.7414 2.74212 4.7163 2.75503 4.69435C2.78081 4.65357 2.8098 4.6222 2.84526 4.60336C2.87749 4.58453 2.90972 4.57197 2.93871 4.56885L2.96127 4.63788Z" fill="#F5F5F5"/>
<path d="M3.64816 4.2986C3.65137 4.30172 3.65461 4.30487 3.66106 4.30799C3.66427 4.31113 3.67072 4.31428 3.67394 4.32052L3.43546 4.63429C3.45479 4.64686 3.47736 4.65624 3.49992 4.66254C3.52246 4.66881 3.54503 4.66881 3.56759 4.66566C3.59015 4.66254 3.60948 4.65624 3.63205 4.64371C3.65137 4.63117 3.67072 4.61861 3.68362 4.59666C3.70617 4.56841 3.71583 4.54016 3.71907 4.50879C3.72228 4.47742 3.71907 4.45232 3.7094 4.42722L3.77384 4.41154C3.78674 4.45547 3.78674 4.49625 3.78031 4.53074C3.77384 4.56529 3.75774 4.59977 3.73197 4.63117C3.7094 4.65939 3.68684 4.68134 3.65782 4.69703C3.62883 4.71274 3.59982 4.72528 3.56759 4.72843C3.53536 4.73158 3.50314 4.73158 3.47091 4.72216C3.43868 4.71274 3.40645 4.70018 3.37746 4.67823C3.34844 4.65624 3.32588 4.63117 3.30655 4.60607C3.29044 4.57782 3.27754 4.5496 3.27109 4.51821C3.26464 4.48684 3.26788 4.45859 3.27432 4.42722C3.28077 4.39585 3.29687 4.3676 3.31622 4.3425C3.33878 4.31428 3.36456 4.28915 3.39034 4.27662C3.41935 4.26093 3.44513 4.25152 3.47412 4.25152C3.50314 4.24837 3.53215 4.25152 3.56114 4.26093C3.59337 4.2672 3.61915 4.27974 3.64816 4.2986ZM3.58692 4.3425C3.54826 4.3174 3.50959 4.30799 3.47091 4.31113C3.43223 4.31428 3.39679 4.33624 3.36456 4.37702C3.35166 4.39585 3.34199 4.41465 3.33555 4.43664C3.32912 4.45859 3.32912 4.47742 3.33233 4.49937C3.33554 4.52136 3.34199 4.54016 3.35166 4.55584C3.36134 4.57467 3.37422 4.59039 3.39034 4.60292L3.58692 4.3425Z" fill="#F5F5F5"/>
<path d="M3.60663 4.09183C3.59052 4.07926 3.5744 4.06673 3.56152 4.05731L3.60342 4.01023C3.61308 4.01649 3.62598 4.02594 3.63886 4.03536C3.65176 4.04474 3.66142 4.05416 3.66787 4.06046C3.65497 4.03221 3.65176 4.00396 3.65497 3.97259C3.65821 3.94122 3.67432 3.91297 3.69686 3.88787C3.70331 3.88161 3.70976 3.87531 3.71298 3.86907C3.71943 3.86277 3.72266 3.85962 3.72909 3.85651L3.77422 3.90671C3.771 3.90986 3.76455 3.91297 3.75811 3.91924C3.75166 3.92554 3.74523 3.93181 3.73876 3.93808C3.72588 3.95064 3.71621 3.96633 3.71298 3.98513C3.70655 4.00081 3.70655 4.01968 3.70976 4.03848C3.71298 4.05731 3.71943 4.07614 3.73233 4.09809C3.74523 4.12008 3.76455 4.13888 3.7871 4.16086L3.97403 4.31773L3.9289 4.37105L3.64209 4.13261C3.63564 4.11378 3.62274 4.10436 3.60663 4.09183Z" fill="#F5F5F5"/>
<path d="M3.7378 3.59283C3.74748 3.60224 3.75391 3.6148 3.75391 3.62736C3.75391 3.63989 3.7507 3.65246 3.74103 3.66185C3.73137 3.67126 3.7217 3.67441 3.7088 3.67756C3.6959 3.67756 3.68303 3.67441 3.67334 3.665C3.66368 3.65558 3.65723 3.64304 3.65723 3.63048C3.65723 3.61793 3.66046 3.60539 3.67013 3.59598C3.67979 3.58655 3.68948 3.58342 3.70235 3.58029C3.71525 3.58029 3.72813 3.58342 3.7378 3.59283ZM4.21476 4.04777L4.16642 4.09797L3.81515 3.77482L3.86349 3.72461L4.21476 4.04777Z" fill="#F5F5F5"/>
<path d="M4.47611 3.79704L4.42131 3.85039L3.94434 3.6433L4.00235 3.58996L4.39876 3.77194L4.23115 3.37661L4.28595 3.32642L4.47611 3.79704Z" fill="#F5F5F5"/>
<path d="M4.7052 3.12898C4.68587 3.1227 4.6601 3.1227 4.63752 3.12583C4.61176 3.13211 4.58918 3.14152 4.56988 3.15721C4.54729 3.17604 4.5312 3.19486 4.52153 3.21683C4.51186 3.2388 4.50541 3.26074 4.50216 3.28271C4.50216 3.30468 4.50541 3.32977 4.51186 3.35174C4.52153 3.37371 4.53441 3.39567 4.55051 3.41762C4.56663 3.43959 4.58597 3.45528 4.60855 3.46783C4.63107 3.48037 4.65365 3.48665 4.6762 3.49293C4.69878 3.49608 4.72454 3.49608 4.74712 3.48978C4.76967 3.48352 4.79222 3.47096 4.81477 3.45528C4.83735 3.43646 4.85669 3.41449 4.86311 3.39253C4.87278 3.37056 4.87603 3.34861 4.87603 3.32664L4.94049 3.32351C4.94049 3.35487 4.93404 3.38625 4.92112 3.41762C4.90824 3.449 4.88569 3.47411 4.85348 3.50234C4.82444 3.52744 4.79222 3.54314 4.76 3.55255C4.72775 3.56196 4.69554 3.56509 4.66332 3.56196C4.6311 3.55881 4.60209 3.5494 4.57309 3.53371C4.54408 3.51803 4.51829 3.49608 4.49574 3.46783C4.47319 3.43959 4.45706 3.41136 4.4474 3.37998C4.43773 3.34861 4.43452 3.31723 4.43773 3.28586C4.44094 3.25448 4.45061 3.22624 4.46673 3.19486C4.48286 3.16662 4.50541 3.13839 4.53763 3.11329C4.56342 3.09446 4.59242 3.07877 4.62785 3.06936C4.66332 3.05995 4.69554 3.05682 4.731 3.06623L4.7052 3.12898Z" fill="#F5F5F5"/>
<path d="M5.26582 2.87764C5.26904 2.88077 5.27228 2.88705 5.27549 2.89018C5.2787 2.89331 5.28195 2.89959 5.28516 2.90587L4.95646 3.1255C4.96934 3.14432 4.98868 3.16 5.00802 3.17256C5.02735 3.1851 5.04669 3.19138 5.06924 3.19766C5.09179 3.20081 5.11437 3.20081 5.13692 3.19766C5.1595 3.19453 5.18205 3.1851 5.20139 3.16941C5.23036 3.1506 5.25294 3.1255 5.26582 3.09725C5.2787 3.06903 5.28195 3.04391 5.28195 3.01568L5.34963 3.01881C5.34642 3.06275 5.33675 3.10353 5.31738 3.13491C5.29807 3.16628 5.27228 3.19453 5.24006 3.21647C5.21106 3.23531 5.17881 3.251 5.14659 3.25728C5.11437 3.26354 5.08212 3.26354 5.0499 3.25728C5.01768 3.251 4.98868 3.23844 4.95967 3.22275C4.93067 3.20394 4.90812 3.18197 4.88554 3.15375C4.8662 3.1255 4.85008 3.09412 4.84365 3.06275C4.83719 3.03137 4.83398 3 4.84044 2.96862C4.84686 2.93724 4.85653 2.909 4.87266 2.8839C4.88878 2.85881 4.91133 2.83371 4.94034 2.81487C4.97255 2.79292 5.00156 2.78037 5.03378 2.77409C5.06603 2.76781 5.09503 2.76781 5.12404 2.77409C5.15304 2.78037 5.17881 2.79292 5.2046 2.81174C5.22715 2.82743 5.24973 2.8494 5.26582 2.87764ZM5.19493 2.89646C5.16593 2.86193 5.13368 2.83999 5.09503 2.83058C5.05636 2.82115 5.01447 2.83058 4.97255 2.85881C4.95322 2.87136 4.93709 2.88705 4.92746 2.90587C4.91454 2.92469 4.90812 2.94352 4.90487 2.96234C4.90163 2.98118 4.90163 3.00313 4.90487 3.02196C4.90812 3.04078 4.91454 3.05962 4.92746 3.07843L5.19493 2.89646Z" fill="#F5F5F5"/>
<path d="M6.22916 2.25345C6.25492 2.30364 6.26783 2.35071 6.27104 2.40091C6.27426 2.45111 6.26783 2.49818 6.25171 2.54209C6.23561 2.58603 6.21303 2.62681 6.1776 2.66447C6.14535 2.70212 6.10347 2.73035 6.05512 2.75545C6.00678 2.77741 5.95844 2.7931 5.90688 2.79625C5.85533 2.79938 5.8102 2.7931 5.76185 2.77741C5.71672 2.76172 5.67484 2.73976 5.63616 2.70525C5.59749 2.67388 5.56848 2.63309 5.54269 2.5829C5.51693 2.53268 5.50402 2.48562 5.5008 2.43542C5.49759 2.38522 5.50401 2.33816 5.52014 2.29423C5.53626 2.25031 5.55881 2.20952 5.59428 2.17187C5.62649 2.13422 5.66841 2.10598 5.71672 2.08088C5.76507 2.05892 5.81341 2.04323 5.865 2.04009C5.91655 2.03695 5.96165 2.04323 6.00999 2.05892C6.05512 2.0746 6.09701 2.09657 6.13568 2.13108C6.17436 2.16559 6.20658 2.20638 6.22916 2.25345ZM6.15823 2.28796C6.13893 2.25031 6.11635 2.21893 6.08734 2.19069C6.05834 2.16245 6.02612 2.1405 5.98744 2.12794C5.95201 2.11226 5.91331 2.10598 5.87142 2.10912C5.82953 2.10912 5.78762 2.12167 5.74573 2.1405C5.70384 2.15932 5.66841 2.18756 5.64259 2.21579C5.61683 2.24717 5.59749 2.28169 5.58782 2.3162C5.57815 2.35384 5.57494 2.3915 5.57815 2.42915C5.5814 2.46993 5.59428 2.50759 5.61361 2.54524C5.63295 2.5829 5.6555 2.61425 5.68451 2.6425C5.71351 2.67073 5.74573 2.68956 5.78119 2.70525C5.81665 2.71781 5.85533 2.72722 5.89721 2.72409C5.9391 2.72409 5.98099 2.71153 6.02291 2.69269C6.06479 2.67388 6.10025 2.64563 6.12602 2.6174C6.15181 2.58603 6.17115 2.55152 6.18081 2.517C6.19048 2.47934 6.1937 2.4417 6.19048 2.40405C6.19048 2.36326 6.1776 2.32561 6.15823 2.28796Z" fill="#F5F5F5"/>
<path d="M6.35556 2.14993C6.3491 2.1311 6.33943 2.11542 6.33301 2.09973L6.39423 2.07777C6.40066 2.09032 6.40711 2.10286 6.41357 2.11542C6.42002 2.1311 6.42324 2.14052 6.42969 2.14993H6.43291C6.43612 2.11856 6.44579 2.09032 6.46512 2.06522C6.48446 2.04012 6.51025 2.0213 6.54247 2.00874C6.55214 2.0056 6.5586 2.00247 6.56505 2.00247C6.57148 1.99933 6.5779 1.99933 6.58757 1.99933L6.6037 2.06208C6.60048 2.06208 6.59403 2.06208 6.58436 2.06522C6.57469 2.06836 6.56505 2.0715 6.55535 2.07463C6.53926 2.08091 6.52314 2.09032 6.51025 2.10286C6.49734 2.11542 6.48767 2.1311 6.48125 2.14679C6.47479 2.16562 6.47158 2.18444 6.47158 2.20955C6.47158 2.23464 6.478 2.25975 6.49092 2.28799L6.5779 2.51388L6.51347 2.53898L6.37489 2.19386C6.37168 2.18444 6.36523 2.16876 6.35556 2.14993Z" fill="#F5F5F5"/>
<path d="M6.87778 2.50409C6.90357 2.52293 6.93579 2.53547 6.97125 2.54174C7.00668 2.54802 7.04536 2.54802 7.08082 2.53547C7.11301 2.52606 7.13883 2.5135 7.16135 2.49783C7.18075 2.48212 7.19684 2.46331 7.20648 2.44448C7.21615 2.42566 7.21936 2.4037 7.21936 2.37859C7.21936 2.35663 7.21615 2.33153 7.20976 2.30643L7.18075 2.22172H7.17747C7.16784 2.25623 7.1485 2.28447 7.12271 2.30956C7.0937 2.33467 7.06469 2.35035 7.02923 2.36291C6.9938 2.37232 6.95834 2.37859 6.92612 2.37546C6.89387 2.37232 6.8649 2.36291 6.83586 2.35035C6.8101 2.33467 6.78431 2.31584 6.76497 2.29074C6.74563 2.26564 6.72954 2.23741 6.71987 2.20289C6.7102 2.17151 6.70696 2.14014 6.7102 2.10877C6.71341 2.07739 6.72308 2.04915 6.73596 2.02091C6.74884 1.99267 6.77143 1.97071 6.79719 1.94875C6.82298 1.92679 6.85523 1.9111 6.89066 1.90168C6.92291 1.89227 6.95834 1.88914 6.99701 1.89541C7.03248 1.90168 7.0647 1.91738 7.0937 1.93933H7.09691L7.07436 1.8609L7.14201 1.84207L7.2774 2.2876C7.28383 2.31271 7.29032 2.3378 7.29032 2.36918C7.29032 2.39742 7.28707 2.42879 7.27419 2.45703C7.26128 2.48527 7.24197 2.5135 7.21615 2.53547C7.18714 2.56056 7.1485 2.5794 7.09691 2.59509C7.05503 2.60763 7.00993 2.61078 6.96801 2.60763C6.92291 2.60137 6.8842 2.58881 6.84877 2.56999L6.87778 2.50409ZM6.79076 2.17779C6.79719 2.20289 6.8101 2.22172 6.82298 2.24054C6.83586 2.25936 6.85523 2.27506 6.87457 2.2876C6.89387 2.30015 6.91645 2.30643 6.94222 2.30956C6.96801 2.31271 6.9938 2.30956 7.01956 2.30329C7.04536 2.29702 7.06791 2.28447 7.08724 2.26878C7.10661 2.25309 7.12271 2.23741 7.13241 2.21544C7.14201 2.19348 7.15171 2.17465 7.15171 2.14955C7.15496 2.12445 7.15171 2.09935 7.14201 2.07425C7.13562 2.04915 7.12271 2.03033 7.10661 2.0115C7.09049 1.99267 7.07436 1.97699 7.05181 1.96757C7.03248 1.95503 7.00993 1.94875 6.98413 1.94561C6.95834 1.94247 6.93579 1.94561 6.91 1.95189C6.88099 1.9613 6.85844 1.97071 6.83911 1.9864C6.81977 2.00209 6.80689 2.02091 6.79719 2.03974C6.78755 2.05857 6.78109 2.08053 6.78109 2.10563C6.78109 2.13072 6.78431 2.15583 6.79076 2.17779Z" fill="#F5F5F5"/>
<path d="M7.6533 1.90192C7.6436 1.86114 7.62747 1.83604 7.60168 1.82035C7.57595 1.80466 7.5437 1.80152 7.50181 1.81093C7.47277 1.81722 7.44698 1.82663 7.42443 1.84231C7.40185 1.858 7.38576 1.87369 7.36963 1.89251L7.32129 1.85486C7.33741 1.8329 7.35999 1.81093 7.38903 1.79211C7.41797 1.77329 7.45347 1.76073 7.49532 1.75132C7.52112 1.74505 7.54691 1.74505 7.56946 1.74819C7.59204 1.75132 7.61459 1.7576 7.6339 1.76701C7.6533 1.77642 7.66942 1.79211 7.68551 1.81093C7.69833 1.82976 7.70803 1.85486 7.71452 1.8831L7.75637 2.0839C7.75959 2.10273 7.76607 2.11841 7.76929 2.13724C7.77568 2.15607 7.77896 2.17175 7.78541 2.18431L7.72416 2.19685C7.72094 2.18744 7.71452 2.17489 7.71124 2.16234C7.70803 2.14979 7.70482 2.13724 7.70161 2.12469H7.69833C7.68224 2.1592 7.66293 2.18744 7.64038 2.20627C7.61459 2.2251 7.58555 2.23764 7.54694 2.24705C7.5276 2.25019 7.50824 2.25333 7.4889 2.25019C7.46956 2.25019 7.45026 2.24392 7.43089 2.23764C7.41155 2.22823 7.39543 2.21881 7.38254 2.20313C7.36963 2.18744 7.35999 2.16548 7.35351 2.14038C7.34711 2.10586 7.35029 2.07449 7.36324 2.04939C7.37612 2.02429 7.39864 2.00547 7.42764 1.98978C7.45668 1.97409 7.49211 1.9584 7.53082 1.94898C7.56946 1.93957 7.61138 1.92702 7.6533 1.92075V1.90192ZM7.64681 1.97409C7.6178 1.98036 7.58876 1.98664 7.56303 1.99291C7.53403 2.00233 7.50824 2.01174 7.4889 2.02115C7.46638 2.0337 7.45026 2.04625 7.43737 2.06194C7.42443 2.07763 7.42125 2.09645 7.42443 2.11841C7.42764 2.1341 7.43416 2.14665 7.44377 2.1592C7.45347 2.16861 7.46311 2.17489 7.47599 2.18117C7.4889 2.18744 7.50181 2.18744 7.51469 2.18744C7.5276 2.18744 7.54042 2.18744 7.55655 2.18431C7.58234 2.17803 7.60168 2.17175 7.6178 2.1592C7.6339 2.14665 7.65002 2.1341 7.65969 2.11528C7.66942 2.09959 7.67581 2.08076 7.67903 2.06194C7.68224 2.04312 7.68224 2.02115 7.67581 2.00233L7.66942 1.96781L7.64681 1.97409Z" fill="#F5F5F5"/>
<path d="M7.93093 1.68827C7.93414 1.70082 7.93742 1.71337 7.9406 1.72907C7.94381 1.74475 7.94381 1.7573 7.94705 1.76671H7.95027C7.95994 1.73848 7.97927 1.71337 8.00828 1.69141C8.03728 1.66945 8.06953 1.66003 8.10172 1.65376C8.16297 1.64748 8.21132 1.66003 8.24354 1.69141C8.27897 1.72278 8.29837 1.76671 8.30476 1.82633L8.34019 2.11184L8.26933 2.12125L8.23711 1.86398C8.2339 1.83887 8.23062 1.81692 8.2242 1.79809C8.21777 1.77926 8.20811 1.76357 8.19841 1.74789C8.18552 1.73534 8.17264 1.72593 8.15327 1.71965C8.13718 1.71337 8.11463 1.71337 8.08884 1.71337C8.06953 1.71651 8.05341 1.71965 8.03728 1.72907C8.02119 1.73848 8.00507 1.75102 7.9954 1.76671C7.98245 1.7824 7.97606 1.80436 7.96964 1.82633C7.96315 1.85142 7.96315 1.87653 7.96636 1.9079L7.9954 2.14636L7.92451 2.15577L7.87941 1.79181C7.87941 1.77926 7.87616 1.76357 7.87289 1.74161C7.86967 1.72278 7.86649 1.70396 7.86328 1.68827H7.93093Z" fill="#F5F5F5"/>
<path d="M8.53415 1.45298C8.53415 1.46553 8.53094 1.47808 8.52127 1.48749C8.51164 1.4969 8.50194 1.50318 8.48902 1.50318C8.47621 1.50318 8.46651 1.50004 8.45363 1.49063C8.44392 1.48122 8.4375 1.47181 8.4375 1.45612C8.4375 1.44357 8.44071 1.43101 8.45041 1.4216C8.46008 1.41219 8.46972 1.40591 8.48263 1.40591C8.49554 1.40591 8.50515 1.40905 8.51806 1.41846C8.52776 1.42788 8.53415 1.44043 8.53415 1.45298ZM8.55998 2.10559L8.48902 2.10872L8.46329 1.63809L8.53415 1.63495L8.55998 2.10559Z" fill="#F5F5F5"/>
<path d="M9.03238 1.67236L8.74875 2.03945L9.04847 2.03318V2.0928L8.65527 2.09907V2.05201L8.93566 1.68177L8.66176 1.68492V1.62844L9.02917 1.62216L9.03238 1.67236Z" fill="#F5F5F5"/>
<path d="M9.48067 1.81736C9.48394 1.77657 9.47424 1.7452 9.45494 1.72323C9.4356 1.70127 9.40338 1.68872 9.36146 1.68558C9.33246 1.68244 9.30666 1.68558 9.28093 1.695C9.25511 1.70441 9.23259 1.71382 9.21647 1.72951L9.18097 1.68244C9.20355 1.66362 9.22931 1.65107 9.26153 1.63852C9.29375 1.62911 9.33246 1.62283 9.37438 1.62597C9.40011 1.62911 9.42593 1.63224 9.44524 1.64166C9.46785 1.65107 9.48715 1.66362 9.50325 1.67617C9.51937 1.69185 9.53228 1.71068 9.53871 1.73264C9.54838 1.75461 9.54838 1.77971 9.54838 1.80795L9.53228 2.01189C9.53228 2.03071 9.52907 2.04953 9.52907 2.06836C9.52907 2.08719 9.52907 2.10288 9.53228 2.11856L9.47106 2.11542C9.47106 2.10288 9.46785 2.09032 9.46785 2.07777C9.46785 2.06522 9.46785 2.05267 9.46785 2.03699H9.46457C9.43881 2.06836 9.41302 2.08719 9.38398 2.09974C9.35497 2.11229 9.32276 2.11542 9.28093 2.11229C9.26153 2.11229 9.2422 2.10601 9.22289 2.09974C9.20355 2.09346 9.18746 2.08405 9.17133 2.0715C9.15521 2.05895 9.14233 2.04326 9.13584 2.02444C9.1262 2.00561 9.12299 1.98365 9.1262 1.95855C9.12942 1.9209 9.13912 1.8958 9.16167 1.87384C9.18097 1.85501 9.20677 1.83932 9.2422 1.83305C9.27441 1.82363 9.30994 1.82049 9.35179 1.82049C9.39368 1.82049 9.4356 1.82049 9.48067 1.82363V1.81736ZM9.45494 1.88325C9.42593 1.88011 9.39692 1.88011 9.36789 1.88011C9.33888 1.88011 9.31312 1.88325 9.28733 1.88952C9.26153 1.8958 9.2422 1.90521 9.22607 1.91462C9.20998 1.92717 9.20028 1.94286 9.20028 1.96482C9.20028 1.98051 9.20028 1.9962 9.20677 2.00875C9.21319 2.0213 9.21968 2.03071 9.23259 2.03699C9.2422 2.04326 9.25511 2.04953 9.26799 2.05581C9.28093 2.06209 9.29375 2.06209 9.30994 2.06209C9.33567 2.06522 9.35825 2.06209 9.37759 2.05267C9.39692 2.0464 9.41302 2.03385 9.42911 2.0213C9.44202 2.00875 9.45494 1.99306 9.46136 1.97424C9.47106 1.95541 9.47424 1.93658 9.47424 1.91462L9.47745 1.88011L9.45494 1.88325Z" fill="#F5F5F5"/>
<path d="M9.95459 1.76359L9.81923 1.74476L9.78052 2.01772C9.77734 2.03655 9.77734 2.0491 9.78052 2.06165C9.7838 2.07421 9.78701 2.08362 9.79022 2.08989C9.79665 2.09616 9.80313 2.10244 9.8128 2.10557C9.82244 2.10871 9.83214 2.11186 9.84178 2.11186C9.85148 2.11186 9.86115 2.11186 9.874 2.11186C9.88366 2.11186 9.89658 2.10871 9.90628 2.10557L9.89979 2.16519C9.88688 2.16833 9.874 2.17147 9.86115 2.17147C9.84827 2.17147 9.83214 2.17147 9.81602 2.16833C9.80313 2.16519 9.79022 2.16205 9.77734 2.15892C9.76443 2.15264 9.75158 2.14636 9.73867 2.13381C9.729 2.1244 9.71936 2.10871 9.71287 2.09303C9.70645 2.07734 9.70645 2.05538 9.70966 2.03028L9.7483 1.73848L9.64844 1.72594L9.65483 1.66946L9.75479 1.68201L9.7741 1.55023L9.84505 1.55964L9.82565 1.69142L9.96104 1.71025L9.95459 1.76359Z" fill="#F5F5F5"/>
<path d="M10.0619 2.19985L9.99414 2.1873L10.078 1.72294L10.1456 1.73549L10.0619 2.19985ZM10.1908 1.56293C10.1875 1.57548 10.1811 1.58803 10.1682 1.5943C10.1553 1.60057 10.1456 1.60371 10.1327 1.60057C10.1199 1.59744 10.1102 1.59116 10.1005 1.58175C10.0909 1.57234 10.0876 1.55979 10.0909 1.5441C10.0941 1.53155 10.1005 1.519 10.1134 1.51272C10.1263 1.50645 10.136 1.50331 10.1488 1.50645C10.1617 1.50959 10.1715 1.51587 10.1811 1.52528C10.1908 1.53469 10.194 1.54724 10.1908 1.56293Z" fill="#F5F5F5"/>
<path d="M10.7342 2.10898C10.7246 2.14349 10.7117 2.17487 10.6924 2.19997C10.673 2.22507 10.6505 2.24704 10.6214 2.26272C10.5924 2.27841 10.5634 2.28782 10.528 2.2941C10.4926 2.30037 10.4603 2.29723 10.4216 2.28782C10.3862 2.27841 10.354 2.26586 10.325 2.24704C10.2959 2.22821 10.2766 2.20311 10.2573 2.17801C10.2412 2.14977 10.2283 2.12153 10.2251 2.09016C10.2186 2.05879 10.2218 2.02427 10.2315 1.98976C10.2412 1.95524 10.254 1.92387 10.2734 1.89877C10.2927 1.87367 10.3153 1.85171 10.3443 1.83602C10.3733 1.82032 10.4023 1.81091 10.4377 1.80464C10.47 1.8015 10.5055 1.8015 10.5441 1.81091C10.5795 1.82032 10.6117 1.83288 10.6408 1.85171C10.6698 1.87053 10.6891 1.89563 10.7085 1.92073C10.7246 1.94897 10.7374 1.97721 10.7407 2.00858C10.7471 2.03996 10.7439 2.07447 10.7342 2.10898ZM10.6634 2.09016C10.6698 2.06506 10.673 2.03996 10.6698 2.01486C10.6666 1.98976 10.6601 1.96779 10.6505 1.94897C10.6408 1.93014 10.6247 1.91132 10.6054 1.89563C10.586 1.87995 10.5634 1.87053 10.5344 1.86425C10.5055 1.85798 10.4796 1.85798 10.457 1.86112C10.4313 1.86425 10.412 1.87367 10.3926 1.88622C10.3733 1.89877 10.3572 1.91445 10.3411 1.93642C10.3282 1.95838 10.3153 1.98034 10.3121 2.00544C10.3056 2.03055 10.3024 2.05565 10.3056 2.08074C10.3088 2.10585 10.3153 2.12781 10.325 2.14664C10.3346 2.16546 10.3508 2.18428 10.3701 2.19997C10.3894 2.21566 10.412 2.22507 10.441 2.23135C10.47 2.23762 10.4958 2.23762 10.5183 2.23448C10.5441 2.23135 10.5634 2.22193 10.5828 2.20938C10.6021 2.19684 10.6182 2.18114 10.6344 2.15918C10.6472 2.14035 10.6569 2.11526 10.6634 2.09016Z" fill="#F5F5F5"/>
<path d="M11.005 1.94564C11.0018 1.95819 10.9985 1.97074 10.9953 1.98642C10.9921 2.00211 10.9857 2.01466 10.9824 2.02407H10.9857C11.0082 2.00211 11.0372 1.98957 11.0727 1.98329C11.1081 1.97701 11.1403 1.98015 11.1726 1.98957C11.2306 2.00839 11.2693 2.03977 11.2854 2.08369C11.3015 2.12762 11.3015 2.17468 11.2822 2.23115L11.1854 2.50412L11.1178 2.48215L11.2048 2.23743C11.2145 2.21547 11.2177 2.1935 11.221 2.17154C11.2241 2.15271 11.2241 2.13389 11.2177 2.11506C11.2145 2.09938 11.2048 2.08369 11.1919 2.07114C11.179 2.05859 11.1597 2.04918 11.1371 2.03977C11.121 2.03349 11.1017 2.03035 11.0824 2.03349C11.0631 2.03349 11.0437 2.03977 11.0275 2.04918C11.0082 2.05859 10.9921 2.07427 10.976 2.0931C10.9599 2.11193 10.947 2.13703 10.9373 2.16526L10.8567 2.39431L10.7891 2.37234L10.9115 2.02407C10.9148 2.01153 10.9212 1.99584 10.9277 1.97701C10.9341 1.95819 10.9373 1.93936 10.9438 1.92368L11.005 1.94564Z" fill="#F5F5F5"/>
<path d="M12.1721 2.82742C12.1527 2.83683 12.1301 2.84623 12.1076 2.85251C12.085 2.85879 12.0592 2.86192 12.0335 2.86192C12.0077 2.86192 11.9786 2.85879 11.9529 2.85251C11.9239 2.84623 11.8949 2.83683 11.8659 2.82114C11.8175 2.79604 11.7756 2.76467 11.7434 2.72701C11.7112 2.68936 11.6854 2.64857 11.6725 2.60466C11.6564 2.56072 11.6531 2.51366 11.6564 2.46346C11.6596 2.41326 11.6757 2.3662 11.7015 2.316C11.7273 2.26894 11.7595 2.22815 11.7982 2.19677C11.8368 2.16539 11.8788 2.1403 11.9239 2.12775C11.969 2.11206 12.0173 2.10892 12.0689 2.11206C12.1204 2.11519 12.1688 2.13089 12.2172 2.15284C12.2397 2.16539 12.2622 2.17795 12.2848 2.19363C12.3041 2.20932 12.3235 2.22815 12.3396 2.24697C12.3558 2.2658 12.3686 2.28462 12.3783 2.30658C12.388 2.32855 12.3944 2.34737 12.3976 2.36933L12.3202 2.38188C12.3202 2.36933 12.3138 2.35365 12.3106 2.33796C12.3041 2.32227 12.2945 2.30658 12.2848 2.29089C12.2751 2.27521 12.259 2.25952 12.2461 2.24697C12.23 2.23442 12.2139 2.22187 12.1914 2.20932C12.1495 2.18736 12.1076 2.17795 12.0657 2.17481C12.0238 2.17167 11.9851 2.17795 11.9496 2.19363C11.9142 2.20618 11.8788 2.22815 11.8498 2.25638C11.8208 2.28462 11.7949 2.316 11.7756 2.35365C11.7563 2.3913 11.7434 2.42895 11.7402 2.46973C11.7338 2.51053 11.737 2.54817 11.7466 2.58582C11.7563 2.62348 11.7756 2.65798 11.8014 2.68936C11.8272 2.72073 11.8594 2.74583 11.9013 2.7678C11.94 2.78663 11.9786 2.79604 12.0205 2.79917C12.0625 2.80232 12.1043 2.78976 12.143 2.77095L12.1721 2.82742Z" fill="#F5F5F5"/>
<path d="M12.7329 3.04037C12.7135 3.07175 12.6878 3.09372 12.662 3.11253C12.633 3.13137 12.604 3.14391 12.575 3.15019C12.5427 3.15647 12.5105 3.15647 12.4783 3.15019C12.446 3.14391 12.4106 3.13137 12.3816 3.10941C12.3494 3.09059 12.3236 3.06547 12.3043 3.04037C12.2849 3.01215 12.272 2.9839 12.2656 2.95253C12.2591 2.92115 12.2591 2.88978 12.2656 2.8584C12.272 2.82702 12.2849 2.79565 12.3043 2.76427C12.3236 2.73603 12.3461 2.71093 12.3752 2.69211C12.4042 2.6733 12.4331 2.66074 12.4621 2.65446C12.4944 2.64818 12.5266 2.64818 12.5589 2.65446C12.5911 2.66074 12.6233 2.6733 12.6556 2.69211C12.6878 2.71093 12.7135 2.73603 12.7329 2.76427C12.7522 2.79252 12.7651 2.82074 12.7716 2.85212C12.778 2.8835 12.778 2.91487 12.7716 2.94625C12.7651 2.97762 12.7522 3.009 12.7329 3.04037ZM12.6717 2.99959C12.6878 2.97762 12.6974 2.95566 12.7007 2.93056C12.7071 2.90546 12.7071 2.8835 12.7039 2.86153C12.7007 2.83958 12.691 2.81762 12.6781 2.79565C12.6652 2.77368 12.6458 2.75799 12.6233 2.74231C12.6007 2.72662 12.575 2.71721 12.5524 2.71406C12.5266 2.71093 12.5041 2.71093 12.4815 2.71721C12.4589 2.72349 12.4364 2.7329 12.417 2.74859C12.3977 2.76427 12.3784 2.78309 12.3655 2.80506C12.3494 2.82702 12.3397 2.84899 12.3365 2.87409C12.3301 2.89919 12.3301 2.92115 12.3333 2.94312C12.3365 2.96509 12.3461 2.98703 12.3591 3.00587C12.372 3.02469 12.3913 3.04352 12.4138 3.05919C12.4364 3.07488 12.4621 3.08431 12.4847 3.08744C12.5105 3.09059 12.5331 3.09059 12.5556 3.08431C12.5782 3.07803 12.6007 3.06862 12.62 3.05293C12.6394 3.04037 12.6556 3.02156 12.6717 2.99959Z" fill="#F5F5F5"/>
<path d="M13.0416 2.97169C13.0351 2.9811 13.0254 2.99366 13.0157 3.00622C13.006 3.01876 12.9996 3.02816 12.9932 3.03759H12.9964C13.0254 3.02503 13.0576 3.01876 13.0931 3.02503C13.1285 3.03131 13.1607 3.04387 13.1866 3.06269C13.2349 3.10034 13.2607 3.14113 13.2607 3.18819C13.2639 3.23525 13.2446 3.27917 13.2091 3.32623L13.0254 3.55214L12.9706 3.51136L13.135 3.30742C13.1511 3.2886 13.1639 3.26976 13.1736 3.25094C13.1833 3.2321 13.1898 3.21329 13.1898 3.1976C13.1898 3.17878 13.1866 3.1631 13.1801 3.14741C13.1704 3.13172 13.1575 3.11603 13.1382 3.10034C13.1221 3.08778 13.106 3.08151 13.0899 3.07523C13.0705 3.06897 13.0512 3.06897 13.0319 3.0721C13.0125 3.07523 12.9899 3.08466 12.9706 3.09719C12.9481 3.10975 12.9287 3.12857 12.9094 3.15367L12.7579 3.34507L12.7031 3.30427L12.9352 3.01563C12.9416 3.00622 12.9545 2.99366 12.9642 2.97484C12.977 2.95915 12.9867 2.94346 12.9964 2.93091L13.0416 2.97169Z" fill="#F5F5F5"/>
<path d="M13.5834 3.48957L13.4835 3.40172L13.2966 3.60565C13.2837 3.61821 13.2773 3.63076 13.2708 3.64017C13.2644 3.64959 13.2644 3.66213 13.2644 3.66843C13.2644 3.67784 13.2676 3.68723 13.2741 3.69353C13.2804 3.69976 13.2869 3.70921 13.2934 3.71548C13.2998 3.72174 13.3094 3.72801 13.3191 3.73116C13.3288 3.73743 13.3385 3.74058 13.3481 3.7437L13.3094 3.79078C13.2966 3.78763 13.2837 3.78136 13.2741 3.77195C13.2611 3.76568 13.2514 3.75626 13.2385 3.74684C13.2289 3.73743 13.2192 3.72801 13.2128 3.71548C13.2031 3.70291 13.1999 3.69038 13.1967 3.67469C13.1934 3.65898 13.1967 3.6433 13.1999 3.62761C13.2063 3.61193 13.216 3.59311 13.2353 3.57429L13.4352 3.35466L13.361 3.28876L13.3997 3.24485L13.4738 3.31073L13.564 3.21347L13.6156 3.26053L13.5254 3.35779L13.6253 3.44564L13.5834 3.48957Z" fill="#F5F5F5"/>
<path d="M13.6711 3.56511C13.684 3.54942 13.6969 3.53688 13.7098 3.52432L13.7549 3.57138C13.7452 3.58079 13.7388 3.59335 13.7259 3.60276C13.7162 3.61532 13.7065 3.62473 13.7001 3.63099C13.7291 3.62158 13.7613 3.61845 13.7935 3.62473C13.8258 3.63099 13.8548 3.64669 13.8773 3.6718C13.8838 3.67807 13.8902 3.68436 13.8934 3.6906C13.8967 3.6969 13.9031 3.70317 13.9064 3.70943L13.8516 3.75022C13.8484 3.74707 13.8451 3.74083 13.8387 3.73453C13.8323 3.72827 13.8258 3.71885 13.8194 3.71258C13.8065 3.70002 13.7904 3.6906 13.7742 3.68436C13.7581 3.67807 13.7388 3.67492 13.7194 3.67492C13.7001 3.67492 13.6808 3.68121 13.6582 3.69375C13.6356 3.70317 13.6131 3.722 13.5905 3.74395L13.4165 3.91339L13.3682 3.86633L13.6324 3.60589C13.6453 3.59335 13.655 3.58079 13.6711 3.56511Z" fill="#F5F5F5"/>
<path d="M14.1026 4.28974C14.0736 4.31172 14.0446 4.33052 14.0124 4.33994C13.9802 4.34936 13.9479 4.35562 13.9157 4.35251C13.8834 4.34936 13.8512 4.34306 13.8222 4.3274C13.7932 4.31172 13.7642 4.28974 13.7384 4.26149C13.7126 4.23327 13.6965 4.20502 13.6836 4.17365C13.674 4.14229 13.6675 4.11089 13.6708 4.07952C13.674 4.04815 13.6804 4.01678 13.6965 3.98853C13.7126 3.96028 13.732 3.93206 13.761 3.91011C13.79 3.88813 13.819 3.8693 13.8512 3.85988C13.8834 3.8505 13.9157 3.8442 13.9479 3.84735C13.9802 3.8505 14.0124 3.85676 14.0414 3.87245C14.0704 3.88813 14.0994 3.91011 14.1219 3.93521C14.1477 3.96343 14.1639 3.99168 14.1767 4.02305C14.1864 4.05442 14.1928 4.08579 14.1896 4.11718C14.1864 4.14855 14.1799 4.17992 14.1639 4.20814C14.1542 4.23954 14.1315 4.26779 14.1026 4.28974ZM14.0542 4.23639C14.0736 4.2207 14.0897 4.19875 14.1026 4.17992C14.1154 4.15797 14.1219 4.13599 14.1251 4.11404C14.1284 4.09208 14.1251 4.06695 14.1187 4.045C14.1122 4.02305 14.0994 4.00107 14.08 3.97912C14.0607 3.95717 14.0414 3.9446 14.0188 3.93206C13.9962 3.92265 13.9737 3.91635 13.9511 3.91635C13.9285 3.91635 13.9028 3.9195 13.8802 3.92892C13.8577 3.93833 13.8351 3.9509 13.8158 3.96658C13.7964 3.98227 13.7803 4.00422 13.7674 4.02305C13.7545 4.045 13.7481 4.06695 13.7449 4.08893C13.7416 4.11089 13.7449 4.13599 13.7513 4.15797C13.7577 4.17992 13.7706 4.2019 13.79 4.22385C13.8093 4.24581 13.8286 4.26149 13.8512 4.27091C13.8738 4.28032 13.8963 4.28659 13.9189 4.28659C13.9414 4.28659 13.9672 4.28347 13.9898 4.27405C14.0124 4.26464 14.0349 4.25207 14.0542 4.23639Z" fill="#F5F5F5"/>
<path d="M14.0341 4.59694L13.9922 4.54362L14.611 4.0824L14.6529 4.13575L14.0341 4.59694Z" fill="#F5F5F5"/>
<path d="M14.3248 4.9985L14.2861 4.93576L14.9114 4.56552L15.0242 4.74437C15.0435 4.77259 15.0564 4.80399 15.0661 4.83221C15.0757 4.86046 15.0789 4.88871 15.0789 4.91378C15.0789 4.93891 15.0693 4.96401 15.0532 4.98911C15.0371 5.01106 15.0145 5.03305 14.9855 5.05185C14.9629 5.06438 14.9404 5.07383 14.9146 5.07695C14.892 5.0801 14.8663 5.0801 14.8437 5.07383C14.8211 5.06753 14.7985 5.05815 14.7792 5.04558C14.7599 5.03305 14.7406 5.01421 14.7245 4.99223L14.5375 5.34679L14.4892 5.27148L14.6762 4.92949L14.6116 4.82594L14.3248 4.9985ZM14.6729 4.79458L14.7406 4.90124C14.7728 4.95144 14.805 4.98281 14.8405 4.9985C14.8759 5.01421 14.9114 5.01106 14.9501 4.98911C14.9694 4.97658 14.9855 4.96401 14.9952 4.94833C15.0049 4.93264 15.0081 4.91693 15.0113 4.89809C15.0113 4.87929 15.0081 4.86046 15.0016 4.83851C14.9951 4.81653 14.9823 4.79457 14.9694 4.77259L14.9017 4.66281L14.6729 4.79458Z" fill="#F5F5F5"/>
<path d="M15.0044 5.69187C15.0012 5.69499 14.9947 5.69499 14.9914 5.69814C14.985 5.70126 14.9818 5.70126 14.9753 5.70441L14.7981 5.35615C14.7755 5.36868 14.7594 5.38125 14.7433 5.39693C14.7272 5.41262 14.7175 5.43145 14.7111 5.4534C14.7047 5.47535 14.7014 5.49419 14.7014 5.51929C14.7014 5.54127 14.7079 5.56322 14.7208 5.58517C14.7369 5.61654 14.7594 5.64164 14.7852 5.65735C14.8109 5.67301 14.84 5.68245 14.8658 5.68557L14.8529 5.74834C14.8077 5.73892 14.7691 5.72324 14.7401 5.69814C14.7111 5.67616 14.6853 5.64479 14.6691 5.6103C14.6531 5.5789 14.6434 5.5475 14.6434 5.51617C14.6402 5.4848 14.6466 5.4534 14.6563 5.42515C14.6659 5.39693 14.6821 5.36868 14.7047 5.34358C14.7272 5.31848 14.753 5.29965 14.7884 5.28396C14.8207 5.26828 14.8529 5.25886 14.8883 5.25571C14.9206 5.2526 14.9528 5.25572 14.9818 5.26516C15.0108 5.27458 15.0398 5.29026 15.0624 5.30907C15.0881 5.3279 15.1075 5.35615 15.1236 5.38437C15.1397 5.41888 15.1494 5.45025 15.1494 5.4785C15.1494 5.50987 15.1461 5.53812 15.1364 5.56322C15.1268 5.58832 15.1107 5.61342 15.0881 5.63537C15.0591 5.66047 15.0334 5.67616 15.0044 5.69187ZM14.9914 5.61969C15.0302 5.59774 15.0591 5.56949 15.072 5.53185C15.0881 5.49419 15.0817 5.4534 15.0624 5.41262C15.0527 5.39067 15.0398 5.37498 15.0205 5.36242C15.0044 5.34985 14.9851 5.33732 14.9657 5.33105C14.9463 5.32475 14.9238 5.32163 14.9044 5.32163C14.8851 5.32163 14.8625 5.32475 14.8432 5.3342L14.9914 5.61969Z" fill="#F5F5F5"/>
<path d="M14.6147 6.05627L14.5889 5.9935L15.2721 5.73935L15.2979 5.80212L15.2206 5.83033V5.83348C15.256 5.83975 15.2882 5.85543 15.3172 5.88054C15.343 5.90564 15.3656 5.93389 15.3752 5.96525C15.3881 5.99977 15.3946 6.03429 15.3946 6.06566C15.3946 6.09705 15.3849 6.12842 15.372 6.15664C15.3591 6.18489 15.3397 6.20999 15.314 6.23194C15.2882 6.25392 15.2592 6.26961 15.227 6.28214C15.1947 6.29471 15.1593 6.30098 15.1271 6.30098C15.0948 6.30098 15.0626 6.29471 15.0336 6.28214C15.0046 6.26961 14.9788 6.25392 14.9563 6.22882C14.9337 6.20684 14.9144 6.17547 14.9014 6.14096C14.8886 6.10959 14.8853 6.07507 14.8886 6.04056C14.8918 6.00604 14.9047 5.97155 14.924 5.9433V5.94015L14.6147 6.05627ZM15.2012 6.21314C15.227 6.20369 15.2495 6.19116 15.2689 6.17547C15.2882 6.15979 15.3043 6.14096 15.314 6.12212C15.3236 6.10332 15.3301 6.07822 15.3333 6.05627C15.3333 6.03117 15.3301 6.00604 15.3204 5.98094C15.3107 5.95584 15.2978 5.937 15.2785 5.9182C15.2624 5.90252 15.2399 5.88683 15.2173 5.88054C15.1947 5.87112 15.1722 5.868 15.1464 5.868C15.1206 5.868 15.0948 5.87112 15.0691 5.88368C15.0433 5.8931 15.0207 5.90564 15.0013 5.92447C14.982 5.94015 14.9691 5.95899 14.9595 5.98094C14.9498 6.00292 14.9434 6.02487 14.9434 6.04997C14.9434 6.07507 14.9466 6.09705 14.9563 6.12212C14.9659 6.14722 14.982 6.16921 14.9981 6.18801C15.0143 6.20369 15.0368 6.21626 15.0562 6.22567C15.0787 6.23509 15.1013 6.23824 15.1271 6.23509C15.1496 6.22568 15.1754 6.22256 15.2012 6.21314Z" fill="#F5F5F5"/>
<path d="M15.3977 6.86548C15.3622 6.87493 15.3268 6.87804 15.2946 6.87493C15.2623 6.87181 15.2301 6.86236 15.2043 6.84668C15.1753 6.83099 15.1528 6.80907 15.1302 6.78394C15.1109 6.75884 15.0948 6.72744 15.0851 6.68984C15.0754 6.65532 15.0722 6.62077 15.0754 6.5894C15.0786 6.55492 15.0883 6.52667 15.1044 6.49842C15.1206 6.4702 15.1399 6.44822 15.1689 6.42627C15.1947 6.40743 15.2269 6.39175 15.2623 6.38233C15.2978 6.37292 15.3333 6.36977 15.3655 6.37292C15.3977 6.37607 15.4299 6.38548 15.4557 6.40117C15.4847 6.41685 15.5073 6.4388 15.5298 6.4639C15.5524 6.48903 15.5652 6.52037 15.5749 6.55492C15.5846 6.5894 15.5879 6.62392 15.5846 6.65847C15.5814 6.69296 15.5717 6.7212 15.5556 6.74942C15.5395 6.77767 15.5202 6.79962 15.4912 6.82161C15.4654 6.84044 15.4332 6.85612 15.3977 6.86548ZM15.3783 6.79651C15.4042 6.79018 15.4267 6.77767 15.4493 6.76514C15.4686 6.74942 15.488 6.73374 15.4976 6.71491C15.5105 6.69607 15.5169 6.67415 15.5234 6.64902C15.5266 6.62392 15.5266 6.59882 15.5169 6.57372C15.5105 6.54862 15.4976 6.52352 15.4815 6.50784C15.4654 6.48903 15.4461 6.47647 15.4267 6.4639C15.4042 6.45448 15.3816 6.44822 15.3558 6.4451C15.33 6.44195 15.3043 6.4451 15.2785 6.45137C15.2527 6.45763 15.2301 6.4702 15.2076 6.48273C15.1882 6.49842 15.1689 6.51413 15.1592 6.53294C15.1463 6.55177 15.1399 6.57372 15.1335 6.59882C15.1302 6.62392 15.1302 6.6459 15.1399 6.67415C15.1463 6.69919 15.1592 6.72432 15.1753 6.74312C15.1915 6.76202 15.2108 6.77767 15.2301 6.78706C15.2527 6.79651 15.2752 6.80277 15.3011 6.80586C15.3268 6.80586 15.3526 6.80277 15.3783 6.79651Z" fill="#F5F5F5"/>
<path d="M15.6081 6.95638C15.6274 6.95327 15.6468 6.947 15.6629 6.94382L15.6758 7.00662C15.6629 7.00977 15.65 7.01285 15.6339 7.01912C15.6178 7.0223 15.6049 7.02542 15.5953 7.02854V7.03168C15.6243 7.03798 15.6533 7.05367 15.6758 7.07874C15.6984 7.10072 15.7145 7.12897 15.7209 7.16349C15.7241 7.1729 15.7241 7.17917 15.7241 7.18541C15.7241 7.19174 15.7241 7.20109 15.7241 7.20742L15.6565 7.21054C15.6565 7.20742 15.6565 7.19797 15.6565 7.18859C15.6565 7.17917 15.6533 7.16972 15.6533 7.16037C15.65 7.14147 15.6436 7.12579 15.6339 7.1101C15.6243 7.09442 15.6113 7.08192 15.592 7.0725C15.5759 7.06305 15.5533 7.05679 15.5308 7.05367C15.5082 7.05049 15.4793 7.05049 15.4469 7.05679L15.2053 7.10387L15.1924 7.03802L15.563 6.96583C15.5727 6.96583 15.5888 6.96268 15.6081 6.95638Z" fill="#F5F5F5"/>
<path d="M15.7102 7.58093L15.6909 7.44913L15.4105 7.48676C15.3911 7.48991 15.3783 7.49306 15.3686 7.49618C15.3589 7.50242 15.3493 7.5056 15.3428 7.51498C15.3364 7.52128 15.3332 7.53067 15.3332 7.54012C15.3332 7.54953 15.3332 7.55892 15.3332 7.56836C15.3332 7.57772 15.3364 7.58717 15.3428 7.59661C15.346 7.60597 15.3525 7.61542 15.3589 7.6248L15.2977 7.63734C15.2912 7.6248 15.2849 7.61542 15.2816 7.60285C15.2784 7.59028 15.2752 7.57772 15.2719 7.56203C15.2687 7.54953 15.2687 7.537 15.2719 7.52128C15.2719 7.5056 15.2784 7.49306 15.2849 7.4805C15.2912 7.46793 15.3042 7.45537 15.3203 7.44598C15.3364 7.43656 15.3557 7.43033 15.3815 7.42712L15.6812 7.38639L15.6683 7.28908L15.7263 7.27969L15.7392 7.37695L15.8745 7.35814L15.8842 7.42712L15.7489 7.44598L15.7683 7.57772L15.7102 7.58093Z" fill="#F5F5F5"/>
<path d="M15.6913 7.97263C15.7106 7.96012 15.7267 7.94444 15.7363 7.92246C15.746 7.90051 15.7525 7.87852 15.7492 7.85027C15.7492 7.83771 15.746 7.82524 15.7428 7.81579C15.7396 7.80322 15.7331 7.79384 15.7267 7.78439C15.7202 7.77497 15.7106 7.76874 15.7009 7.76247C15.6913 7.75614 15.6784 7.75614 15.6655 7.75614C15.6429 7.75929 15.6268 7.76874 15.6171 7.78754C15.6075 7.80634 15.5978 7.83459 15.5945 7.87541C15.5881 7.93187 15.572 7.97263 15.5527 8.00088C15.5333 8.02913 15.5043 8.04481 15.4656 8.04793C15.4366 8.05114 15.4141 8.04793 15.3948 8.03854C15.3754 8.02913 15.3593 8.01656 15.3464 8.00088C15.3336 7.98519 15.3239 7.96639 15.3174 7.94759C15.3109 7.92558 15.3077 7.90677 15.3045 7.88482C15.3013 7.85027 15.3077 7.81579 15.3174 7.7813C15.3303 7.74672 15.3529 7.71854 15.3819 7.69344L15.427 7.7436C15.4076 7.75614 15.3915 7.77497 15.3787 7.80007C15.3658 7.82524 15.3593 7.85027 15.3625 7.87852C15.3625 7.89421 15.3658 7.90677 15.369 7.91934C15.3722 7.93187 15.3787 7.94444 15.3851 7.95383C15.3915 7.96327 15.4012 7.96951 15.4141 7.97581C15.427 7.98207 15.4398 7.98208 15.4559 7.98208C15.4818 7.97896 15.4979 7.96639 15.5108 7.94126C15.5205 7.91619 15.5301 7.88164 15.5366 7.83459C15.5398 7.81891 15.543 7.80634 15.5462 7.78754C15.5494 7.77185 15.5559 7.75614 15.5656 7.7436C15.5752 7.73104 15.5849 7.71854 15.601 7.70912C15.6139 7.69967 15.6332 7.69344 15.6558 7.69029C15.6816 7.68717 15.7009 7.69029 15.7202 7.69967C15.7396 7.70912 15.7525 7.71854 15.7654 7.73422C15.7782 7.7499 15.7879 7.76559 15.7944 7.78439C15.8008 7.80322 15.8073 7.82524 15.8073 7.84716C15.8105 7.87852 15.804 7.91301 15.7912 7.94444C15.7782 7.97581 15.7589 8.00088 15.7331 8.01971L15.6913 7.97263Z" fill="#F5F5F5"/>
<path d="M6.02239 14.6058C6.03851 14.5681 6.04172 14.5367 6.03206 14.5085C6.02239 14.4803 5.99659 14.4583 5.95792 14.4426C5.93216 14.4301 5.90637 14.4238 5.87736 14.4238C5.8516 14.4238 5.8258 14.4269 5.80325 14.4332L5.78713 14.3767C5.81289 14.3673 5.84514 14.3642 5.88057 14.3642C5.91603 14.3673 5.9515 14.3767 5.99017 14.3924C6.01272 14.4018 6.03527 14.4175 6.05136 14.4332C6.06752 14.4489 6.08361 14.4677 6.09328 14.4865C6.10295 14.5054 6.10616 14.5274 6.10616 14.5524C6.10616 14.5744 6.09974 14.5995 6.08686 14.6277L5.99659 14.8129C5.99017 14.8285 5.9805 14.8474 5.97404 14.8662C5.96759 14.885 5.96116 14.9007 5.95792 14.9132L5.89991 14.8882C5.90315 14.8756 5.90636 14.8662 5.91279 14.8536C5.91603 14.841 5.92249 14.8285 5.92891 14.816H5.9257C5.89348 14.8348 5.85802 14.8443 5.82902 14.8474C5.7968 14.8474 5.76455 14.841 5.72909 14.8254C5.71299 14.8191 5.6969 14.8066 5.68074 14.794C5.66465 14.7815 5.65177 14.7658 5.6421 14.7501C5.63243 14.7344 5.62598 14.7156 5.62598 14.6937C5.62598 14.6716 5.62919 14.6497 5.6421 14.6277C5.65823 14.5963 5.67753 14.5713 5.70657 14.5619C5.73233 14.5493 5.76134 14.5462 5.7968 14.5493C5.82902 14.5524 5.86445 14.5619 5.90315 14.5775C5.94183 14.5932 5.9805 14.6089 6.01918 14.6246L6.02239 14.6058ZM5.97404 14.6623C5.94825 14.6497 5.92249 14.6403 5.89348 14.6309C5.86445 14.6215 5.83869 14.6152 5.8161 14.612C5.79034 14.6089 5.771 14.6089 5.74846 14.6152C5.72909 14.6215 5.71299 14.634 5.70333 14.6528C5.6969 14.6685 5.69366 14.6811 5.69366 14.6937C5.69366 14.7062 5.70011 14.7187 5.70657 14.7281C5.71299 14.7376 5.72266 14.7501 5.73233 14.7564C5.742 14.7658 5.75491 14.7721 5.76779 14.7783C5.79034 14.7878 5.81289 14.794 5.83223 14.794C5.85481 14.794 5.87415 14.7909 5.89027 14.7846C5.90958 14.7783 5.9257 14.7658 5.93861 14.7533C5.9515 14.7407 5.96438 14.7219 5.97404 14.703L5.99017 14.6716L5.97404 14.6623Z" fill="#F5F5F5"/>
<path d="M6.19432 15.0075L6.12988 14.9824L6.31034 14.5463L6.37481 14.5714L6.19432 15.0075ZM6.45861 14.4082C6.45216 14.4208 6.44249 14.4301 6.42958 14.4334C6.4167 14.4365 6.40381 14.4396 6.39415 14.4334C6.38123 14.4301 6.37481 14.4208 6.36838 14.4082C6.36193 14.3957 6.36193 14.3831 6.36838 14.3705C6.37481 14.358 6.38448 14.3486 6.39736 14.3455C6.41027 14.3423 6.42315 14.3392 6.43282 14.3455C6.4457 14.3486 6.45216 14.358 6.45861 14.3705C6.46183 14.3831 6.46183 14.3957 6.45861 14.4082Z" fill="#F5F5F5"/>
<path d="M6.82934 14.8412C6.82288 14.8193 6.81321 14.8005 6.79712 14.7816C6.781 14.7628 6.75841 14.7503 6.73265 14.7409C6.70686 14.7314 6.6811 14.7283 6.6553 14.7314C6.62951 14.7346 6.60696 14.7409 6.58762 14.7503C6.56508 14.7628 6.54898 14.7754 6.53283 14.7974C6.51673 14.8162 6.50385 14.8381 6.49739 14.8632C6.48773 14.8884 6.48451 14.9134 6.48451 14.9385C6.48451 14.9636 6.49094 14.9856 6.50061 15.0075C6.51028 15.0295 6.52316 15.0484 6.54253 15.064C6.56186 15.0797 6.58441 15.0923 6.61017 15.1017C6.63921 15.1111 6.66497 15.1142 6.69077 15.1111C6.71332 15.1079 6.7359 15.0985 6.7552 15.086L6.79388 15.1393C6.76487 15.155 6.7359 15.1676 6.70365 15.1707C6.67143 15.1738 6.63276 15.1707 6.59408 15.1581C6.55541 15.1456 6.5264 15.1299 6.50061 15.1079C6.47481 15.086 6.45551 15.0609 6.43938 15.0327C6.42647 15.0044 6.41683 14.973 6.41683 14.9417C6.41362 14.9103 6.42005 14.8758 6.43293 14.8412C6.44584 14.8068 6.46196 14.7785 6.48451 14.7534C6.50706 14.7283 6.52961 14.7095 6.55862 14.6969C6.58762 14.6844 6.61984 14.675 6.65209 14.6718C6.68755 14.6687 6.72298 14.675 6.75841 14.6875C6.79066 14.6969 6.81646 14.7157 6.84543 14.7378C6.87126 14.7597 6.89056 14.7879 6.90344 14.8193L6.82934 14.8412Z" fill="#F5F5F5"/>
<path d="M6.9222 15.497L6.85449 15.4813L7.02853 14.791L7.09621 14.8067L7.07687 14.8851H7.08011C7.10591 14.8601 7.13812 14.8444 7.17355 14.8349C7.20902 14.8287 7.24448 14.8287 7.27673 14.8349C7.31213 14.8444 7.34438 14.8569 7.37338 14.8757C7.39911 14.8945 7.42172 14.9197 7.43785 14.9447C7.45394 14.9698 7.46364 15.0012 7.46686 15.0326C7.47007 15.064 7.47007 15.0985 7.46037 15.133C7.45073 15.1675 7.43785 15.1957 7.41851 15.224C7.39911 15.2491 7.37659 15.271 7.3508 15.2899C7.32507 15.3056 7.29603 15.3181 7.2606 15.3212C7.22832 15.3275 7.19289 15.3244 7.15746 15.315C7.12521 15.3056 7.09299 15.293 7.06399 15.2679C7.03498 15.2459 7.01564 15.2177 7.00276 15.1832H6.99952L6.9222 15.497ZM7.38951 15.1173C7.39593 15.0922 7.39911 15.0671 7.39593 15.042C7.39272 15.0169 7.38629 14.995 7.37659 14.9761C7.36693 14.9573 7.3508 14.9385 7.33146 14.9228C7.31213 14.9071 7.28955 14.8977 7.2606 14.8914C7.23481 14.8851 7.20902 14.8851 7.18325 14.8882C7.15746 14.8914 7.13488 14.9008 7.11554 14.9133C7.09621 14.9259 7.07687 14.9416 7.06399 14.9635C7.04786 14.9824 7.03819 15.0075 7.03177 15.0326C7.02531 15.0577 7.0221 15.0828 7.02853 15.1079C7.03498 15.133 7.03819 15.155 7.05111 15.1738C7.06399 15.1926 7.08011 15.2114 7.09945 15.2271C7.11876 15.2428 7.14134 15.2522 7.16713 15.2585C7.1961 15.2647 7.2219 15.2679 7.24448 15.2616C7.27024 15.2585 7.28955 15.2491 7.30895 15.2365C7.32825 15.224 7.34438 15.2083 7.35726 15.1863C7.37338 15.1644 7.38302 15.1424 7.38951 15.1173Z" fill="#F5F5F5"/>
<path d="M7.94004 15.1391C7.94652 15.0983 7.94004 15.0669 7.9207 15.045C7.90139 15.023 7.87239 15.0073 7.83047 15.001C7.80143 14.9979 7.7757 14.9979 7.74991 15.0041C7.72412 15.0105 7.70157 15.0198 7.68226 15.0355L7.65004 14.9854C7.67256 14.9696 7.70157 14.9571 7.737 14.9508C7.77249 14.9445 7.80795 14.9414 7.84978 14.9477C7.8756 14.9508 7.89818 14.9571 7.9207 14.9696C7.94325 14.9791 7.95944 14.9948 7.97553 15.0105C7.99166 15.0261 8.00129 15.045 8.00775 15.0701C8.01417 15.092 8.01417 15.1172 8.01096 15.1454L7.97874 15.3493C7.97553 15.3681 7.97229 15.3869 7.97229 15.4058C7.96904 15.4246 7.96904 15.4403 7.97229 15.456L7.91103 15.4465C7.91103 15.434 7.91103 15.4215 7.91103 15.4089C7.91103 15.3963 7.91431 15.3838 7.91431 15.3712H7.91103C7.8853 15.3995 7.8563 15.4183 7.82726 15.4277C7.79825 15.4372 7.76282 15.4403 7.72412 15.434C7.70478 15.4308 7.68869 15.4246 7.66935 15.4183C7.65004 15.4089 7.63392 15.3995 7.62101 15.3869C7.60813 15.3744 7.59521 15.3556 7.58879 15.3367C7.5823 15.3179 7.57912 15.2959 7.5823 15.2677C7.58879 15.2332 7.6017 15.2049 7.62425 15.1861C7.64677 15.1673 7.67256 15.1548 7.70799 15.1516C7.74024 15.1454 7.77892 15.1454 7.81756 15.1485C7.85948 15.1516 7.90139 15.1548 7.94325 15.1642L7.94004 15.1391ZM7.90782 15.2049C7.87881 15.2018 7.84978 15.1987 7.82083 15.1956C7.79183 15.1924 7.76603 15.1956 7.74024 15.1987C7.71448 15.2018 7.69517 15.2081 7.67905 15.2207C7.66286 15.2332 7.65326 15.2489 7.64677 15.2709C7.64355 15.2866 7.64677 15.3022 7.65004 15.3148C7.65326 15.3274 7.66286 15.3367 7.67256 15.3461C7.68226 15.3556 7.69517 15.3619 7.70799 15.365C7.7209 15.3712 7.73378 15.3744 7.7467 15.3744C7.77249 15.3775 7.79504 15.3775 7.81435 15.3712C7.83368 15.365 7.85305 15.3556 7.86918 15.3461C7.8853 15.3336 7.89818 15.3179 7.90782 15.3022C7.91749 15.2834 7.92394 15.2645 7.92716 15.2457L7.93361 15.2113L7.90782 15.2049Z" fill="#F5F5F5"/>
<path d="M8.2534 15.431C8.2534 15.4435 8.24698 15.4561 8.23407 15.4686C8.22115 15.478 8.20827 15.4843 8.19215 15.4812C8.17602 15.478 8.16321 15.4718 8.15351 15.4623C8.14384 15.4498 8.14062 15.4373 8.14062 15.4247C8.14062 15.4122 8.14711 15.3996 8.15993 15.387C8.17284 15.3745 8.18572 15.3714 8.20185 15.3745C8.21797 15.3745 8.23085 15.3839 8.24055 15.3933C8.25016 15.4027 8.25668 15.4153 8.2534 15.431Z" fill="#F5F5F5"/>
<path d="M8.90759 15.2741C8.90759 15.3087 8.89789 15.3432 8.88498 15.3714C8.87213 15.3996 8.85276 15.4279 8.83024 15.4467C8.80763 15.4687 8.77869 15.4844 8.74641 15.4969C8.71419 15.5094 8.67873 15.5126 8.64009 15.5126C8.60141 15.5126 8.56916 15.5032 8.53694 15.4906C8.50473 15.4781 8.47893 15.4593 8.45638 15.4373C8.4338 15.4153 8.41768 15.3871 8.40483 15.3557C8.39195 15.3243 8.38867 15.293 8.38867 15.2553C8.38867 15.2208 8.39837 15.1863 8.41128 15.158C8.42417 15.1298 8.4435 15.1016 8.46602 15.0827C8.48863 15.0608 8.51764 15.0451 8.54986 15.0326C8.58207 15.02 8.61751 15.0169 8.65294 15.0169C8.69164 15.0169 8.72386 15.0263 8.75608 15.0388C8.78833 15.0514 8.81412 15.0702 8.83667 15.0922C8.85925 15.1141 8.87534 15.1423 8.88826 15.1737C8.90438 15.2051 8.91081 15.2365 8.90759 15.2741ZM8.83346 15.271C8.83346 15.2459 8.83024 15.2208 8.82373 15.1957C8.81733 15.1737 8.80442 15.1518 8.78833 15.133C8.7722 15.1141 8.75286 15.1016 8.73028 15.0891C8.70773 15.0796 8.68194 15.0733 8.65294 15.0702C8.62399 15.0702 8.5982 15.0733 8.57559 15.0827C8.55307 15.0922 8.53373 15.1047 8.51764 15.1204C8.50151 15.1361 8.48863 15.158 8.47893 15.18C8.4693 15.202 8.46281 15.2271 8.46281 15.2522C8.46281 15.2773 8.46602 15.3024 8.47251 15.3275C8.47893 15.3494 8.49181 15.3714 8.50794 15.3903C8.52403 15.4091 8.54337 15.4216 8.56274 15.431C8.58207 15.4404 8.61108 15.4467 8.63681 15.4467C8.66585 15.4467 8.69164 15.4436 8.71419 15.4341C8.73677 15.4248 8.75608 15.4122 8.7722 15.3965C8.78833 15.3808 8.80121 15.3589 8.81091 15.3369C8.83025 15.3212 8.83346 15.2961 8.83346 15.271Z" fill="#F5F5F5"/>
<path d="M9.05825 15.0856C9.05825 15.0667 9.05503 15.0479 9.05176 15.0322L9.1195 15.0291C9.12271 15.0416 9.12271 15.0573 9.12271 15.0699C9.12271 15.0856 9.12589 15.0981 9.12589 15.1075H9.12911C9.14202 15.0793 9.16136 15.0573 9.18715 15.0385C9.21294 15.0196 9.24516 15.0103 9.27741 15.0103C9.28702 15.0103 9.2935 15.0103 9.3032 15.0103C9.30963 15.0103 9.3193 15.0103 9.32575 15.0134L9.3193 15.0793C9.31602 15.0793 9.30963 15.0761 9.29993 15.0761C9.29029 15.0761 9.28062 15.0761 9.27092 15.0761C9.25159 15.0761 9.23549 15.0793 9.21615 15.0887C9.20003 15.0949 9.18394 15.1075 9.17102 15.1232C9.15814 15.1388 9.14851 15.1577 9.14202 15.1797C9.13559 15.2016 9.13238 15.2299 9.13238 15.2612L9.14202 15.5028L9.07116 15.5059L9.05825 15.1388C9.05825 15.1232 9.05825 15.1075 9.05825 15.0856Z" fill="#F5F5F5"/>
<path d="M9.50656 15.5752C9.52908 15.5972 9.55812 15.616 9.59355 15.6285C9.62904 15.6411 9.66447 15.6474 9.70311 15.6411C9.73861 15.638 9.76434 15.6285 9.79016 15.616C9.81268 15.6035 9.83205 15.5878 9.84499 15.5689C9.85781 15.5501 9.86751 15.5313 9.87072 15.5062C9.8739 15.4842 9.87718 15.4591 9.8739 15.4341L9.8643 15.3462H9.86108C9.84496 15.3776 9.82238 15.4027 9.79016 15.4215C9.75791 15.4403 9.72572 15.4529 9.69026 15.456C9.65156 15.4591 9.61934 15.4591 9.58712 15.4497C9.5549 15.4403 9.52587 15.4278 9.50335 15.409C9.48074 15.3901 9.45822 15.3681 9.44534 15.3399C9.42921 15.3117 9.41951 15.2803 9.4163 15.2458C9.41309 15.2113 9.4163 15.1799 9.42272 15.1485C9.43242 15.1172 9.44534 15.092 9.46464 15.0669C9.48395 15.0419 9.50656 15.023 9.53557 15.0073C9.5646 14.9916 9.59682 14.9822 9.63546 14.9759C9.67089 14.9728 9.70639 14.9759 9.74182 14.9885C9.77725 15.0011 9.80625 15.0199 9.82887 15.0481H9.83205L9.82238 14.9666L9.8933 14.9602L9.94486 15.4246C9.94804 15.4497 9.94804 15.4779 9.94486 15.5062C9.94165 15.5344 9.93195 15.5627 9.91264 15.5909C9.89652 15.616 9.87072 15.6411 9.83847 15.6599C9.80625 15.6788 9.76434 15.6914 9.7096 15.6976C9.66447 15.7039 9.62255 15.6976 9.5807 15.685C9.53878 15.6725 9.50007 15.6537 9.46785 15.6285L9.50656 15.5752ZM9.48074 15.2426C9.48395 15.2678 9.49047 15.2897 9.50007 15.3117C9.50977 15.3337 9.52587 15.3493 9.54199 15.365C9.55812 15.3807 9.5807 15.3901 9.60321 15.3995C9.62576 15.4058 9.65156 15.409 9.68059 15.4058C9.70639 15.4027 9.73212 15.3964 9.75152 15.3838C9.77404 15.3713 9.79016 15.3588 9.80625 15.3399C9.82238 15.3211 9.83205 15.3023 9.83847 15.2772C9.84496 15.2552 9.84817 15.2301 9.84499 15.2019C9.84172 15.1768 9.83526 15.1548 9.82238 15.1329C9.80947 15.1109 9.79659 15.0952 9.77725 15.0795C9.75791 15.0638 9.73861 15.0544 9.71278 15.045C9.69026 15.0387 9.66447 15.0356 9.63868 15.0387C9.60967 15.0419 9.58391 15.0481 9.5646 15.0607C9.54199 15.0732 9.52587 15.0889 9.51298 15.1077C9.50007 15.1266 9.49047 15.1454 9.48395 15.1705C9.48074 15.1925 9.47755 15.2175 9.48074 15.2426Z" fill="#F5F5F5"/>
<path d="M10.1383 15.4183L10.0771 15.4089L10.2447 14.6371L10.3059 14.6465L10.1383 15.4183Z" fill="#F5F5F5"/>
<path d="M10.6733 14.9194C10.6572 14.9037 10.6379 14.8912 10.6153 14.8848C10.5928 14.8786 10.567 14.8786 10.5411 14.8848C10.5283 14.888 10.5186 14.8912 10.5057 14.8974C10.4928 14.9037 10.4832 14.91 10.4767 14.9162C10.4703 14.9257 10.4638 14.9351 10.4606 14.9445C10.4574 14.9539 10.4574 14.9664 10.4606 14.979C10.4671 15.001 10.4799 15.0135 10.4993 15.0229C10.5186 15.0292 10.5509 15.0323 10.5928 15.0323C10.6507 15.0292 10.6958 15.0354 10.728 15.0511C10.7603 15.0668 10.7797 15.0919 10.7893 15.1264C10.7958 15.1516 10.7958 15.1767 10.7893 15.1955C10.7829 15.2143 10.7732 15.2331 10.7603 15.2488C10.7474 15.2645 10.728 15.277 10.7087 15.2865C10.6894 15.2959 10.6668 15.3053 10.6475 15.3084C10.6121 15.3178 10.5767 15.3178 10.5379 15.3116C10.5025 15.3053 10.4671 15.2896 10.4381 15.2645L10.4832 15.2112C10.4993 15.23 10.5218 15.2425 10.5476 15.2488C10.5734 15.2582 10.6024 15.2582 10.6314 15.252C10.6475 15.2488 10.6604 15.2425 10.6733 15.2394C10.6862 15.2331 10.6958 15.2269 10.7055 15.2174C10.7152 15.2081 10.7184 15.1986 10.7249 15.186C10.728 15.1735 10.728 15.161 10.7249 15.1453C10.7184 15.1202 10.7023 15.1045 10.6765 15.0982C10.6507 15.0919 10.6121 15.0888 10.5637 15.0888C10.5476 15.0888 10.5315 15.0888 10.5154 15.0888C10.4993 15.0888 10.4832 15.0825 10.4671 15.0762C10.451 15.07 10.4381 15.0606 10.4251 15.048C10.4123 15.0354 10.4026 15.0198 10.3993 14.9978C10.3929 14.9758 10.3929 14.9539 10.3993 14.9351C10.4058 14.9162 10.4123 14.9005 10.4251 14.8848C10.4381 14.8692 10.4542 14.8567 10.4735 14.8472C10.4928 14.8378 10.5121 14.8284 10.5347 14.8252C10.567 14.819 10.6024 14.8158 10.6346 14.8252C10.6701 14.8315 10.6958 14.8472 10.7184 14.8692L10.6733 14.9194Z" fill="#F5F5F5"/>
<path d="M11.3724 14.8379C11.3853 14.8724 11.3885 14.9038 11.3853 14.9383C11.382 14.9697 11.3756 15.001 11.3595 15.0293C11.3466 15.0575 11.3241 15.0826 11.2983 15.1046C11.2725 15.1265 11.2402 15.1422 11.2048 15.1548C11.1694 15.1673 11.1339 15.1704 11.1016 15.1704C11.0662 15.1673 11.0372 15.1611 11.0082 15.1454C10.9792 15.1328 10.9534 15.1108 10.9309 15.0889C10.9083 15.0638 10.8922 15.0355 10.8793 15.001C10.8664 14.9665 10.8632 14.9351 10.8664 14.9006C10.8697 14.8692 10.8761 14.8379 10.8922 14.8096C10.9051 14.7814 10.9276 14.7563 10.9534 14.7343C10.9792 14.7124 11.0115 14.6967 11.0469 14.6841C11.0823 14.6716 11.1178 14.6684 11.1533 14.6684C11.1887 14.6716 11.2177 14.6779 11.2467 14.6936C11.2757 14.7061 11.3015 14.728 11.3241 14.75C11.3466 14.7751 11.3627 14.8034 11.3724 14.8379ZM11.3047 14.8598C11.2951 14.8347 11.2854 14.8128 11.2693 14.7939C11.2531 14.7751 11.237 14.7594 11.2145 14.7469C11.1951 14.7343 11.1726 14.728 11.1468 14.7249C11.1211 14.7218 11.0952 14.7249 11.0694 14.7343C11.0437 14.7437 11.0211 14.7563 11.0018 14.772C10.9824 14.7877 10.9696 14.8065 10.9599 14.8285C10.9502 14.8504 10.9437 14.8724 10.9437 14.8975C10.9437 14.9226 10.947 14.9477 10.9567 14.9728C10.9663 14.9979 10.976 15.0198 10.9921 15.0387C11.0082 15.0575 11.0243 15.0732 11.0469 15.0858C11.0662 15.0983 11.0888 15.1046 11.1146 15.1077C11.1404 15.1108 11.1629 15.1077 11.1919 15.0983C11.2177 15.0889 11.2402 15.0763 11.2596 15.0606C11.279 15.045 11.2918 15.0261 11.3015 15.0041C11.3111 14.9822 11.3144 14.9602 11.3176 14.9351C11.3144 14.91 11.3111 14.8849 11.3047 14.8598Z" fill="#F5F5F5"/>
<path d="M11.7982 14.5243C11.7788 14.5117 11.7595 14.5054 11.7337 14.5023C11.7079 14.4992 11.6822 14.5054 11.6596 14.5149C11.6338 14.5243 11.6113 14.54 11.5951 14.5588C11.579 14.5776 11.5661 14.5964 11.5565 14.6184C11.55 14.6404 11.5468 14.6623 11.5468 14.6874C11.5468 14.7125 11.5533 14.7376 11.5661 14.7627C11.5758 14.7878 11.5919 14.8098 11.608 14.8254C11.6241 14.8443 11.6435 14.8568 11.6661 14.8663C11.6886 14.8757 11.7112 14.8819 11.7369 14.8819C11.7627 14.8819 11.7885 14.8757 11.8143 14.8663C11.8433 14.8537 11.8659 14.838 11.8787 14.8192C11.8948 14.8004 11.9045 14.7816 11.9077 14.7596L11.9722 14.7752C11.9626 14.8066 11.9497 14.8349 11.9271 14.86C11.9045 14.8851 11.8755 14.907 11.8369 14.9227C11.8014 14.9384 11.766 14.9447 11.7305 14.9478C11.6951 14.9478 11.6661 14.9447 11.6338 14.9321C11.6048 14.9196 11.579 14.9039 11.5533 14.8788C11.5307 14.8568 11.5114 14.8286 11.4953 14.7941C11.4824 14.7627 11.4727 14.7282 11.4727 14.6968C11.4727 14.6655 11.4759 14.6341 11.4888 14.6058C11.5017 14.5776 11.5178 14.5493 11.5436 14.5274C11.5661 14.5023 11.5983 14.4835 11.6338 14.4709C11.6628 14.4584 11.6951 14.4521 11.7337 14.4521C11.7724 14.4521 11.8014 14.4615 11.8337 14.4772L11.7982 14.5243Z" fill="#F5F5F5"/>
</g>
<defs>
<clipPath id="clip0_349_4942">
<rect width="16.3767" height="15.7468" fill="white" transform="translate(0.730469 0.5)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,24 @@
{
"metricGraphCategory": {
"brokerMetrics": {
"title": "Broker Metrics",
"description": "The Kafka Broker metrics here inform you of data loss/delay through unclean leader elections and network throughputs, as well as request fails through request purgatories and timeouts metrics"
},
"consumerMetrics": {
"title": "Consumer Metrics",
"description": "Kafka Consumer metrics provide insights into lag between message production and consumption, success rates and latency of message delivery, and the volume of data consumed."
},
"producerMetrics": {
"title": "Producer Metrics",
"description": "Kafka Producers send messages to brokers for storage and distribution by topic. These metrics inform you of the volume and rate of data sent, and the success rate of message delivery."
},
"brokerJVMMetrics": {
"title": "Broker JVM Metrics",
"description": "Kafka brokers are Java applications that expose JVM metrics to inform on the broker's system health. Garbage collection metrics like those below provide key insights into free memory, broker performance, and heap size. You need to enable new_gc_metrics for this section to populate."
},
"partitionMetrics": {
"title": "Partition Metrics",
"description": "Kafka partitions are the unit of parallelism in Kafka. These metrics inform you of the number of partitions per topic, the current offset of each partition, the oldest offset, and the number of in-sync replicas."
}
}
}

View File

@@ -1,30 +1,54 @@
{
"breadcrumb": "Messaging Queues",
"header": "Kafka / Overview",
"overview": {
"title": "Start sending data in as little as 20 minutes",
"subtitle": "Connect and Monitor Your Data Streams"
},
"configureConsumer": {
"title": "Configure Consumer",
"description": "Add consumer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"configureProducer": {
"title": "Configure Producer",
"description": "Add producer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"monitorKafka": {
"title": "Monitor kafka",
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
"button": "Get Started"
},
"summarySection": {
"viewDetailsButton": "View Details"
},
"confirmModal": {
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
"okText": "Proceed"
}
}
"breadcrumb": "Messaging Queues",
"header": "Kafka / Overview",
"overview": {
"title": "Start sending data in as little as 20 minutes",
"subtitle": "Connect and Monitor Your Data Streams"
},
"configureConsumer": {
"title": "Configure Consumer",
"description": "Add consumer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"configureProducer": {
"title": "Configure Producer",
"description": "Add producer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"monitorKafka": {
"title": "Monitor kafka",
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
"button": "Get Started"
},
"summarySection": {
"viewDetailsButton": "View Details",
"consumer": {
"title": "Consumer lag view",
"description": "Connect and Monitor Your Data Streams"
},
"producer": {
"title": "Producer latency view",
"description": "Connect and Monitor Your Data Streams"
},
"partition": {
"title": "Partition Latency view",
"description": "Connect and Monitor Your Data Streams"
},
"dropRate": {
"title": "Drop Rate view",
"description": "Connect and Monitor Your Data Streams"
},
"metricPage": {
"title": "Metric View",
"description": "Connect and Monitor Your Data Streams"
}
},
"confirmModal": {
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
"okText": "Proceed"
},
"overviewSummarySection": {
"title": "Monitor Your Data Streams",
"subtitle": "Monitor key Kafka metrics like consumer lag and latency to ensure efficient data flow and troubleshoot in real time."
}
}

View File

@@ -0,0 +1,24 @@
{
"metricGraphCategory": {
"brokerMetrics": {
"title": "Broker Metrics",
"description": "The Kafka Broker metrics here inform you of data loss/delay through unclean leader elections and network throughputs, as well as request fails through request purgatories and timeouts metrics"
},
"consumerMetrics": {
"title": "Consumer Metrics",
"description": "Kafka Consumer metrics provide insights into lag between message production and consumption, success rates and latency of message delivery, and the volume of data consumed."
},
"producerMetrics": {
"title": "Producer Metrics",
"description": "Kafka Producers send messages to brokers for storage and distribution by topic. These metrics inform you of the volume and rate of data sent, and the success rate of message delivery."
},
"brokerJVMMetrics": {
"title": "Broker JVM Metrics",
"description": "Kafka brokers are Java applications that expose JVM metrics to inform on the broker's system health. Garbage collection metrics like those below provide key insights into free memory, broker performance, and heap size. You need to enable new_gc_metrics for this section to populate."
},
"partitionMetrics": {
"title": "Partition Metrics",
"description": "Kafka partitions are the unit of parallelism in Kafka. These metrics inform you of the number of partitions per topic, the current offset of each partition, the oldest offset, and the number of in-sync replicas."
}
}
}

View File

@@ -1,30 +1,54 @@
{
"breadcrumb": "Messaging Queues",
"header": "Kafka / Overview",
"overview": {
"title": "Start sending data in as little as 20 minutes",
"subtitle": "Connect and Monitor Your Data Streams"
},
"configureConsumer": {
"title": "Configure Consumer",
"description": "Add consumer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"configureProducer": {
"title": "Configure Producer",
"description": "Add producer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"monitorKafka": {
"title": "Monitor kafka",
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
"button": "Get Started"
},
"summarySection": {
"viewDetailsButton": "View Details"
},
"confirmModal": {
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
"okText": "Proceed"
}
}
"breadcrumb": "Messaging Queues",
"header": "Kafka / Overview",
"overview": {
"title": "Start sending data in as little as 20 minutes",
"subtitle": "Connect and Monitor Your Data Streams"
},
"configureConsumer": {
"title": "Configure Consumer",
"description": "Add consumer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"configureProducer": {
"title": "Configure Producer",
"description": "Add producer data sources to gain insights and enhance monitoring.",
"button": "Get Started"
},
"monitorKafka": {
"title": "Monitor kafka",
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
"button": "Get Started"
},
"summarySection": {
"viewDetailsButton": "View Details",
"consumer": {
"title": "Consumer lag view",
"description": "Connect and Monitor Your Data Streams"
},
"producer": {
"title": "Producer latency view",
"description": "Connect and Monitor Your Data Streams"
},
"partition": {
"title": "Partition Latency view",
"description": "Connect and Monitor Your Data Streams"
},
"dropRate": {
"title": "Drop Rate view",
"description": "Connect and Monitor Your Data Streams"
},
"metricPage": {
"title": "Metric View",
"description": "Connect and Monitor Your Data Streams"
}
},
"confirmModal": {
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
"okText": "Proceed"
},
"overviewSummarySection": {
"title": "Monitor Your Data Streams",
"subtitle": "Monitor key Kafka metrics like consumer lag and latency to ensure efficient data flow and troubleshoot in real time."
}
}

View File

@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import getLocalStorageApi from 'api/browser/localstorage/get';
import getOrgUser from 'api/user/getOrgUser';
import loginApi from 'api/user/login';
import { Logout } from 'api/utils';
import Spinner from 'components/Spinner';
@@ -8,8 +9,10 @@ import ROUTES from 'constants/routes';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { ReactChild, useEffect, useMemo } from 'react';
import { isEmpty, isNull } from 'lodash-es';
import { ReactChild, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { matchPath, Redirect, useLocation } from 'react-router-dom';
import { Dispatch } from 'redux';
@@ -17,7 +20,9 @@ import { AppState } from 'store/reducers';
import { getInitialUserTokenRefreshToken } from 'store/utils';
import AppActions from 'types/actions';
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
import { Organization } from 'types/api/user/getOrganization';
import AppReducer from 'types/reducer/app';
import { isCloudUser } from 'utils/app';
import { routePermission } from 'utils/permission';
import routes, {
@@ -31,6 +36,19 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const location = useLocation();
const { pathname } = location;
const [isLoading, setIsLoading] = useState<boolean>(true);
const {
org,
orgPreferences,
user,
role,
isUserFetching,
isUserFetchingError,
isLoggedIn: isLoggedInState,
isFetchingOrgPreferences,
} = useSelector<AppState, AppReducer>((state) => state.app);
const mapRoutes = useMemo(
() =>
new Map(
@@ -44,18 +62,23 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
[pathname],
);
const isOnboardingComplete = useMemo(
() =>
orgPreferences?.find(
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
)?.value,
[orgPreferences],
);
const {
data: licensesData,
isFetching: isFetchingLicensesData,
} = useLicense();
const {
isUserFetching,
isUserFetchingError,
isLoggedIn: isLoggedInState,
} = useSelector<AppState, AppReducer>((state) => state.app);
const { t } = useTranslation(['common']);
const isCloudUserVal = isCloudUser();
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
const dispatch = useDispatch<Dispatch<AppActions>>();
@@ -66,6 +89,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
const isLocalStorageLoggedIn =
getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true';
@@ -81,6 +106,68 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
};
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
queryFn: () => {
if (orgData && orgData.id !== undefined) {
return getOrgUser({
orgId: orgData.id,
});
}
return undefined;
},
queryKey: ['getOrgUser'],
enabled: !isEmpty(orgData),
});
const checkFirstTimeUser = (): boolean => {
const users = orgUsers?.payload || [];
const remainingUsers = users.filter(
(user) => user.email !== 'admin@signoz.cloud',
);
return remainingUsers.length === 1;
};
// Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load
const shouldShowOnboarding = (): boolean => {
// Only run this effect if the org users and preferences are loaded
if (!isLoadingOrgUsers && !isFetchingOrgPreferences) {
const isFirstUser = checkFirstTimeUser();
// Redirect to get started if it's not the first user or if the onboarding is complete
return isFirstUser && !isOnboardingComplete;
}
return false;
};
const handleRedirectForOrgOnboarding = (key: string): void => {
if (
isLoggedInState &&
isCloudUserVal &&
!isFetchingOrgPreferences &&
!isLoadingOrgUsers &&
!isEmpty(orgUsers?.payload) &&
!isNull(orgPreferences)
) {
if (key === 'ONBOARDING' && isOnboardingComplete) {
history.push(ROUTES.APPLICATION);
}
const isFirstTimeUser = checkFirstTimeUser();
if (isFirstTimeUser && !isOnboardingComplete) {
history.push(ROUTES.ONBOARDING);
}
}
if (!isCloudUserVal && key === 'ONBOARDING') {
history.push(ROUTES.APPLICATION);
}
};
const handleUserLoginIfTokenPresent = async (
key: keyof typeof ROUTES,
): Promise<void> => {
@@ -102,6 +189,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
response.payload.refreshJwt,
);
handleRedirectForOrgOnboarding(key);
if (
userResponse &&
route &&
@@ -129,7 +218,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
) {
handleUserLoginIfTokenPresent(key);
} else {
// user does have localstorage values
handleRedirectForOrgOnboarding(key);
navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn);
}
@@ -160,6 +249,45 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
}, [isFetchingLicensesData]);
useEffect(() => {
if (org && org.length > 0 && org[0].id !== undefined) {
setOrgData(org[0]);
}
}, [org]);
const handleRouting = (): void => {
const showOrgOnboarding = shouldShowOnboarding();
if (showOrgOnboarding && !isOnboardingComplete && isCloudUserVal) {
history.push(ROUTES.ONBOARDING);
} else {
history.push(ROUTES.APPLICATION);
}
};
useEffect(() => {
const { isPrivate } = currentRoute || {
isPrivate: false,
};
if (isLoggedInState && role && role !== 'ADMIN') {
setIsLoading(false);
}
if (!isPrivate) {
setIsLoading(false);
}
if (
!isEmpty(user) &&
!isFetchingOrgPreferences &&
!isEmpty(orgUsers?.payload) &&
!isNull(orgPreferences)
) {
setIsLoading(false);
}
}, [currentRoute, user, role, orgUsers, orgPreferences]);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
(async (): Promise<void> => {
@@ -181,9 +309,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
handlePrivateRoutes(key);
} else {
// no need to fetch the user and make user fetching false
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
history.push(ROUTES.APPLICATION);
handleRouting();
}
dispatch({
type: UPDATE_USER_IS_FETCH,
@@ -195,7 +322,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
} else if (pathname === ROUTES.HOME_PAGE) {
// routing to application page over root page
if (isLoggedInState) {
history.push(ROUTES.APPLICATION);
handleRouting();
} else {
navigateToLoginIfNotLoggedIn();
}
@@ -208,13 +335,20 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
history.push(ROUTES.SOMETHING_WENT_WRONG);
}
})();
}, [dispatch, isLoggedInState, currentRoute, licensesData]);
}, [
dispatch,
isLoggedInState,
currentRoute,
licensesData,
orgUsers,
orgPreferences,
]);
if (isUserFetchingError) {
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
}
if (isUserFetching) {
if (isUserFetching || isLoading) {
return <Spinner tip="Loading..." />;
}

View File

@@ -2,6 +2,7 @@ import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
@@ -24,13 +25,20 @@ import AlertRuleProvider from 'providers/Alert';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app';
import {
UPDATE_FEATURE_FLAG_RESPONSE,
UPDATE_IS_FETCHING_ORG_PREFERENCES,
UPDATE_ORG_PREFERENCES,
} from 'types/actions/app';
import AppReducer, { User } from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
import PrivateRoute from './Private';
@@ -65,6 +73,41 @@ function App(): JSX.Element {
const isPremiumSupportEnabled =
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
queryFn: () => getAllOrgPreferences(),
queryKey: ['getOrgPreferences'],
enabled: isLoggedInState && role === USER_ROLES.ADMIN,
});
useEffect(() => {
if (orgPreferences && !isLoadingOrgPreferences) {
dispatch({
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
payload: {
isFetchingOrgPreferences: false,
},
});
dispatch({
type: UPDATE_ORG_PREFERENCES,
payload: {
orgPreferences: orgPreferences.payload?.data || null,
},
});
}
}, [orgPreferences, dispatch, isLoadingOrgPreferences]);
useEffect(() => {
if (isLoggedInState && role !== USER_ROLES.ADMIN) {
dispatch({
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
payload: {
isFetchingOrgPreferences: false,
},
});
}
}, [isLoggedInState, role, dispatch]);
const featureResponse = useGetFeatureFlag((allFlags) => {
dispatch({
type: UPDATE_FEATURE_FLAG_RESPONSE,
@@ -182,6 +225,16 @@ function App(): JSX.Element {
}, [isLoggedInState, isOnBasicPlan, user]);
useEffect(() => {
if (pathname === ROUTES.ONBOARDING) {
window.Intercom('update', {
hide_default_launcher: true,
});
} else {
window.Intercom('update', {
hide_default_launcher: false,
});
}
trackPageView(pathname);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pathname]);
@@ -204,6 +257,7 @@ function App(): JSX.Element {
user,
licenseData,
isPremiumSupportEnabled,
pathname,
]);
useEffect(() => {
@@ -239,36 +293,38 @@ function App(): JSX.Element {
return (
<ConfigProvider theme={themeConfig}>
<Router history={history}>
<NotificationProvider>
<PrivateRoute>
<ResourceProvider>
<QueryBuilderProvider>
<DashboardProvider>
<KeyboardHotkeysProvider>
<AlertRuleProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<CompatRouter>
<NotificationProvider>
<PrivateRoute>
<ResourceProvider>
<QueryBuilderProvider>
<DashboardProvider>
<KeyboardHotkeysProvider>
<AlertRuleProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</AlertRuleProvider>
</KeyboardHotkeysProvider>
</DashboardProvider>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>
</NotificationProvider>
<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</AlertRuleProvider>
</KeyboardHotkeysProvider>
</DashboardProvider>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>
</NotificationProvider>
</CompatRouter>
</Router>
</ConfigProvider>
);

View File

@@ -66,8 +66,8 @@ export const Onboarding = Loadable(
() => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'),
);
export const OnboardingV2 = Loadable(
() => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPageV2'),
export const OrgOnboarding = Loadable(
() => import(/* webpackChunkName: "OrgOnboarding" */ 'pages/OrgOnboarding'),
);
export const DashboardPage = Loadable(

View File

@@ -31,8 +31,8 @@ import {
NewDashboardPage,
OldLogsExplorer,
Onboarding,
OnboardingV2,
OrganizationSettings,
OrgOnboarding,
PasswordReset,
PipelinePage,
ServiceMapPage,
@@ -72,7 +72,7 @@ const routes: AppRoutes[] = [
{
path: ROUTES.ONBOARDING,
exact: false,
component: OnboardingV2,
component: OrgOnboarding,
isPrivate: true,
key: 'ONBOARDING',
},

View File

@@ -0,0 +1,39 @@
import { ApiBaseInstance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { ErrorResponse, SuccessResponse } from 'types/api';
export interface OnboardingStatusResponse {
status: string;
data: {
attribute?: string;
error_message?: string;
status?: string;
}[];
}
const getOnboardingStatus = async (props: {
start: number;
end: number;
endpointService?: string;
}): Promise<SuccessResponse<OnboardingStatusResponse> | ErrorResponse> => {
const { endpointService, ...rest } = props;
try {
const response = await ApiBaseInstance.post(
`/messaging-queues/kafka/onboarding/${endpointService || 'consumers'}`,
rest,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG);
}
};
export default getOnboardingStatus;

View File

@@ -119,3 +119,42 @@
color: var(--bg-slate-400) !important;
}
}
.date-time-popover-footer {
border-top: 1px solid var(--bg-ink-200);
padding: 8px 14px;
.timezone-container {
&,
.timezone {
font-family: Inter;
font-size: 12px;
line-height: 16px;
letter-spacing: -0.06px;
}
display: flex;
align-items: center;
color: var(--bg-vanilla-400);
gap: 6px;
.timezone {
cursor: pointer;
padding: 0;
color: var(--bg-vanilla-100);
background-color: transparent;
border: none;
}
}
}
.timezone-badge {
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
border-radius: 2px;
background: rgba(171, 189, 255, 0.04);
color: var(--bg-vanilla-100);
font-size: 12px;
font-weight: 400;
line-height: 16px;
letter-spacing: -0.06px;
cursor: pointer;
}

View File

@@ -15,11 +15,14 @@ import { isValidTimeFormat } from 'lib/getMinMax';
import { defaultTo, isFunction, noop } from 'lodash-es';
import debounce from 'lodash-es/debounce';
import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import {
ChangeEvent,
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
@@ -28,6 +31,8 @@ import { popupContainer } from 'utils/selectPopupContainer';
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
const maxAllowedMinTimeInMonths = 6;
type ViewType = 'datetime' | 'timezone';
const DEFAULT_VIEW: ViewType = 'datetime';
interface CustomTimePickerProps {
onSelect: (value: string) => void;
@@ -81,6 +86,25 @@ function CustomTimePicker({
const location = useLocation();
const [isInputFocused, setIsInputFocused] = useState(false);
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
const { timezone, browserTimezone } = useTimezone();
const activeTimezoneOffset = timezone?.offset;
const isTimezoneOverridden = useMemo(
() => timezone?.offset !== browserTimezone.offset,
[timezone, browserTimezone],
);
const handleViewChange = useCallback(
(newView: 'timezone' | 'datetime'): void => {
if (activeView !== newView) {
setActiveView(newView);
}
setOpen(!open);
},
[activeView, open, setOpen],
);
const getSelectedTimeRangeLabel = (
selectedTime: string,
selectedTimeValue: string,
@@ -132,6 +156,7 @@ function CustomTimePicker({
setOpen(newOpen);
if (!newOpen) {
setCustomDTPickerVisible?.(false);
setActiveView('datetime');
}
};
@@ -281,6 +306,8 @@ function CustomTimePicker({
handleGoLive={defaultTo(handleGoLive, noop)}
options={items}
selectedTime={selectedTime}
activeView={activeView}
setActiveView={setActiveView}
/>
) : (
content
@@ -317,12 +344,23 @@ function CustomTimePicker({
)
}
suffix={
<ChevronDown
size={14}
onClick={(): void => {
setOpen(!open);
}}
/>
<>
{!!isTimezoneOverridden && activeTimezoneOffset && (
<div
className="timezone-badge"
onClick={(e): void => {
e.stopPropagation();
handleViewChange('timezone');
}}
>
<span>{activeTimezoneOffset}</span>
</div>
)}
<ChevronDown
size={14}
onClick={(): void => handleViewChange('datetime')}
/>
</>
}
/>
</Popover>

View File

@@ -1,5 +1,6 @@
import './CustomTimePicker.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import cx from 'classnames';
import ROUTES from 'constants/routes';
@@ -9,10 +10,13 @@ import {
Option,
RelativeDurationSuggestionOptions,
} from 'container/TopNav/DateTimeSelectionV2/config';
import { Clock } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import RangePickerModal from './RangePickerModal';
import TimezonePicker from './TimezonePicker';
interface CustomTimePickerPopoverContentProps {
options: any[];
@@ -26,8 +30,11 @@ interface CustomTimePickerPopoverContentProps {
onSelectHandler: (label: string, value: string) => void;
handleGoLive: () => void;
selectedTime: string;
activeView: 'datetime' | 'timezone';
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
function CustomTimePickerPopoverContent({
options,
setIsOpen,
@@ -37,12 +44,16 @@ function CustomTimePickerPopoverContent({
onSelectHandler,
handleGoLive,
selectedTime,
activeView,
setActiveView,
}: CustomTimePickerPopoverContentProps): JSX.Element {
const { pathname } = useLocation();
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
pathname,
]);
const { timezone } = useTimezone();
const activeTimezoneOffset = timezone?.offset;
function getTimeChips(options: Option[]): JSX.Element {
return (
@@ -63,55 +74,75 @@ function CustomTimePickerPopoverContent({
);
}
return (
<div className="date-time-popover">
<div className="date-time-options">
{isLogsExplorerPage && (
<Button className="data-time-live" type="text" onClick={handleGoLive}>
Live
</Button>
)}
{options.map((option) => (
<Button
type="text"
key={option.label + option.value}
onClick={(): void => {
onSelectHandler(option.label, option.value);
}}
className={cx(
'date-time-options-btn',
customDateTimeVisible
? option.value === 'custom' && 'active'
: selectedTime === option.value && 'active',
)}
return activeView === 'datetime' ? (
<div>
<div className="date-time-popover">
<div className="date-time-options">
{isLogsExplorerPage && (
<Button className="data-time-live" type="text" onClick={handleGoLive}>
Live
</Button>
)}
{options.map((option) => (
<Button
type="text"
key={option.label + option.value}
onClick={(): void => {
onSelectHandler(option.label, option.value);
}}
className={cx(
'date-time-options-btn',
customDateTimeVisible
? option.value === 'custom' && 'active'
: selectedTime === option.value && 'active',
)}
>
{option.label}
</Button>
))}
</div>
<div
className={cx(
'relative-date-time',
selectedTime === 'custom' || customDateTimeVisible
? 'date-picker'
: 'relative-times',
)}
>
{selectedTime === 'custom' || customDateTimeVisible ? (
<RangePickerModal
setCustomDTPickerVisible={setCustomDTPickerVisible}
setIsOpen={setIsOpen}
onCustomDateHandler={onCustomDateHandler}
selectedTime={selectedTime}
/>
) : (
<div className="relative-times-container">
<div className="time-heading">RELATIVE TIMES</div>
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
</div>
)}
</div>
</div>
<div className="date-time-popover-footer">
<div className="timezone-container">
<Clock color={Color.BG_VANILLA_400} height={12} width={12} />
<span className="timezone-text">You are at</span>
<button
type="button"
className="timezone"
onClick={(): void => setActiveView('timezone')}
>
{option.label}
</Button>
))}
</div>
<div
className={cx(
'relative-date-time',
selectedTime === 'custom' || customDateTimeVisible
? 'date-picker'
: 'relative-times',
)}
>
{selectedTime === 'custom' || customDateTimeVisible ? (
<RangePickerModal
setCustomDTPickerVisible={setCustomDTPickerVisible}
setIsOpen={setIsOpen}
onCustomDateHandler={onCustomDateHandler}
selectedTime={selectedTime}
/>
) : (
<div className="relative-times-container">
<div className="time-heading">RELATIVE TIMES</div>
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
</div>
)}
{activeTimezoneOffset}
</button>
</div>
</div>
</div>
) : (
<div className="date-time-popover">
<TimezonePicker setActiveView={setActiveView} setIsOpen={setIsOpen} />
</div>
);
}

View File

@@ -4,6 +4,7 @@ import { DatePicker } from 'antd';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
import dayjs, { Dayjs } from 'dayjs';
import { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -49,6 +50,8 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
}
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
};
const { timezone } = useTimezone();
return (
<div className="custom-date-picker">
<RangePicker
@@ -58,7 +61,10 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
onOk={onModalOkHandler}
// eslint-disable-next-line react/jsx-props-no-spreading
{...(selectedTime === 'custom' && {
defaultValue: [dayjs(minTime / 1000000), dayjs(maxTime / 1000000)],
defaultValue: [
dayjs(minTime / 1000000).tz(timezone.value),
dayjs(maxTime / 1000000).tz(timezone.value),
],
})}
/>
</div>

View File

@@ -0,0 +1,125 @@
// Variables
$font-family: 'Inter';
$border-color: var(--bg-slate-400);
$item-spacing: 8px;
// Mixins
@mixin text-style-base {
font-family: $font-family;
font-style: normal;
font-weight: 400;
}
@mixin flex-center {
display: flex;
align-items: center;
}
.timezone-picker {
width: 532px;
color: var(--bg-vanilla-400);
font-family: $font-family;
&__search {
@include flex-center;
justify-content: space-between;
padding: 12px 14px;
border-bottom: 1px solid $border-color;
}
&__input-container {
@include flex-center;
gap: 6px;
width: -webkit-fill-available;
}
&__input {
@include text-style-base;
width: 100%;
background: transparent;
border: none;
outline: none;
color: var(--bg-vanilla-100);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
padding: 0;
&::placeholder {
color: var(--bg-vanilla-400);
}
}
&__esc-key {
@include text-style-base;
font-size: 8px;
color: var(--bg-vanilla-400);
letter-spacing: -0.04px;
border-radius: 2.286px;
border: 1.143px solid var(--bg-ink-200);
border-bottom-width: 2.286px;
background: var(--bg-ink-400);
padding: 0 1px;
}
&__list {
max-height: 310px;
overflow-y: auto;
}
&__item {
@include flex-center;
justify-content: space-between;
padding: 7.5px 6px 7.5px $item-spacing;
margin: 4px $item-spacing;
cursor: pointer;
background: transparent;
border: none;
width: -webkit-fill-available;
color: var(--bg-vanilla-400);
font-family: $font-family;
&:hover,
&.selected {
border-radius: 2px;
background: rgba(171, 189, 255, 0.04);
color: var(--bg-vanilla-100);
}
&.has-divider {
position: relative;
&::after {
content: '';
position: absolute;
bottom: -2px;
left: -$item-spacing;
right: -$item-spacing;
border-bottom: 1px solid $border-color;
}
}
}
&__name {
@include text-style-base;
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
}
&__offset {
color: var(--bg-vanilla-100);
font-size: 12px;
line-height: 16px;
letter-spacing: -0.06px;
}
}
.timezone-name-wrapper {
@include flex-center;
gap: 6px;
&__selected-icon {
height: 15px;
width: 15px;
}
}

View File

@@ -0,0 +1,156 @@
import './TimezonePicker.styles.scss';
import { Color } from '@signozhq/design-tokens';
import cx from 'classnames';
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { Check, Search } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from 'react';
import { Timezone, TIMEZONE_DATA } from './timezoneUtils';
interface SearchBarProps {
value: string;
onChange: (value: string) => void;
}
interface TimezoneItemProps {
timezone: Timezone;
isSelected?: boolean;
onClick?: () => void;
}
const ICON_SIZE = 14;
function SearchBar({ value, onChange }: SearchBarProps): JSX.Element {
return (
<div className="timezone-picker__search">
<div className="timezone-picker__input-container">
<Search color={Color.BG_VANILLA_400} height={ICON_SIZE} width={ICON_SIZE} />
<input
type="text"
className="timezone-picker__input"
placeholder="Search timezones..."
value={value}
onChange={(e): void => onChange(e.target.value)}
/>
</div>
<kbd className="timezone-picker__esc-key">esc</kbd>
</div>
);
}
function TimezoneItem({
timezone,
isSelected = false,
onClick,
}: TimezoneItemProps): JSX.Element {
return (
<button
type="button"
className={cx('timezone-picker__item', {
selected: isSelected,
'has-divider': timezone.hasDivider,
})}
onClick={onClick}
>
<div className="timezone-name-wrapper">
<div className="timezone-name-wrapper__selected-icon">
{isSelected && (
<Check
color={Color.BG_VANILLA_100}
height={ICON_SIZE}
width={ICON_SIZE}
/>
)}
</div>
<div className="timezone-picker__name">{timezone.name}</div>
</div>
<div className="timezone-picker__offset">{timezone.offset}</div>
</button>
);
}
TimezoneItem.defaultProps = {
isSelected: false,
onClick: undefined,
};
interface TimezonePickerProps {
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
setIsOpen: Dispatch<SetStateAction<boolean>>;
}
function TimezonePicker({
setActiveView,
setIsOpen,
}: TimezonePickerProps): JSX.Element {
const [searchTerm, setSearchTerm] = useState('');
const { timezone, updateTimezone } = useTimezone();
const [selectedTimezone, setSelectedTimezone] = useState<string>(
timezone?.name ?? TIMEZONE_DATA[0].name,
);
const getFilteredTimezones = useCallback((searchTerm: string): Timezone[] => {
const normalizedSearch = searchTerm.toLowerCase();
return TIMEZONE_DATA.filter(
(tz) =>
tz.name.toLowerCase().includes(normalizedSearch) ||
tz.offset.toLowerCase().includes(normalizedSearch) ||
tz.searchIndex.toLowerCase().includes(normalizedSearch),
);
}, []);
const handleCloseTimezonePicker = useCallback(() => {
setActiveView('datetime');
}, [setActiveView]);
const handleTimezoneSelect = useCallback(
(timezone: Timezone) => {
setSelectedTimezone(timezone.name);
updateTimezone(timezone);
handleCloseTimezonePicker();
setIsOpen(false);
},
[handleCloseTimezonePicker, setIsOpen, updateTimezone],
);
// Register keyboard shortcuts
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
useEffect(() => {
registerShortcut(
TimezonePickerShortcuts.CloseTimezonePicker,
handleCloseTimezonePicker,
);
return (): void => {
deregisterShortcut(TimezonePickerShortcuts.CloseTimezonePicker);
};
}, [deregisterShortcut, handleCloseTimezonePicker, registerShortcut]);
return (
<div className="timezone-picker">
<SearchBar value={searchTerm} onChange={setSearchTerm} />
<div className="timezone-picker__list">
{getFilteredTimezones(searchTerm).map((timezone) => (
<TimezoneItem
key={timezone.value}
timezone={timezone}
isSelected={timezone.name === selectedTimezone}
onClick={(): void => handleTimezoneSelect(timezone)}
/>
))}
</div>
</div>
);
}
export default TimezonePicker;

View File

@@ -0,0 +1,142 @@
import { getTimeZones } from '@vvo/tzdb';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
dayjs.extend(timezone);
export interface Timezone {
name: string;
value: string;
offset: string;
searchIndex: string;
hasDivider?: boolean;
}
// Constants
const TIMEZONE_TYPES = {
BROWSER: 'BROWSER',
UTC: 'UTC',
STANDARD: 'STANDARD',
} as const;
type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES];
const UTC_TIMEZONE: Timezone = {
name: 'Coordinated Universal Time — UTC, GMT',
value: 'UTC',
offset: 'UTC',
searchIndex: 'UTC',
hasDivider: true,
};
// Helper functions
const isValidTimezone = (tzName: string): boolean => {
try {
dayjs.tz(dayjs(), tzName);
return true;
} catch {
return false;
}
};
const formatOffset = (offsetMinutes: number): string => {
if (offsetMinutes === 0) return 'UTC';
const hours = Math.floor(Math.abs(offsetMinutes) / 60);
const minutes = Math.abs(offsetMinutes) % 60;
const sign = offsetMinutes > 0 ? '+' : '-';
return `UTC ${sign} ${hours}${
minutes ? `:${minutes.toString().padStart(2, '0')}` : ':00'
}`;
};
const createTimezoneEntry = (
name: string,
offsetMinutes: number,
type: TimezoneType = TIMEZONE_TYPES.STANDARD,
hasDivider = false,
): Timezone => {
const offset = formatOffset(offsetMinutes);
let value = name;
let displayName = name;
switch (type) {
case TIMEZONE_TYPES.BROWSER:
displayName = `Browser time — ${name}`;
value = name;
break;
case TIMEZONE_TYPES.UTC:
displayName = 'Coordinated Universal Time — UTC, GMT';
value = 'UTC';
break;
case TIMEZONE_TYPES.STANDARD:
displayName = name;
value = name;
break;
default:
console.error(`Invalid timezone type: ${type}`);
}
return {
name: displayName,
value,
offset,
searchIndex: offset.replace(/ /g, ''),
...(hasDivider && { hasDivider }),
};
};
const getOffsetByTimezone = (timezone: string): number => {
const dayjsTimezone = dayjs().tz(timezone);
return dayjsTimezone.utcOffset();
};
export const getBrowserTimezone = (): Timezone => {
const browserTz = dayjs.tz.guess();
const browserOffset = getOffsetByTimezone(browserTz);
return createTimezoneEntry(browserTz, browserOffset, TIMEZONE_TYPES.BROWSER);
};
const filterAndSortTimezones = (
allTimezones: ReturnType<typeof getTimeZones>,
browserTzName?: string,
): Timezone[] =>
allTimezones
.filter(
(tz) =>
!tz.name.startsWith('Etc/') &&
isValidTimezone(tz.name) &&
tz.name !== browserTzName,
)
.sort((a, b) => a.name.localeCompare(b.name))
.map((tz) => createTimezoneEntry(tz.name, tz.rawOffsetInMinutes));
const generateTimezoneData = (): Timezone[] => {
const allTimezones = getTimeZones();
const timezones: Timezone[] = [];
// Add browser timezone
const browserTzObject = getBrowserTimezone();
timezones.push(browserTzObject);
// Add UTC timezone with divider
timezones.push(UTC_TIMEZONE);
// Add remaining timezones
timezones.push(...filterAndSortTimezones(allTimezones, browserTzObject.value));
return timezones;
};
export const getTimezoneObjectByTimezoneString = (
timezone: string,
): Timezone => {
const utcOffset = getOffsetByTimezone(timezone);
return createTimezoneEntry(timezone, utcOffset);
};
export const TIMEZONE_DATA = generateTimezoneData();

View File

@@ -1,4 +1,5 @@
import {
_adapters,
BarController,
BarElement,
CategoryScale,
@@ -18,8 +19,10 @@ import {
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import isEqual from 'lodash-es/isEqual';
import { useTimezone } from 'providers/Timezone';
import {
forwardRef,
memo,
@@ -62,6 +65,17 @@ Chart.register(
Tooltip.positioners.custom = TooltipPositionHandler;
// Map of Chart.js time formats to dayjs format strings
const formatMap = {
'HH:mm:ss': 'HH:mm:ss',
'HH:mm': 'HH:mm',
'MM/DD HH:mm': 'MM/DD HH:mm',
'MM/dd HH:mm': 'MM/DD HH:mm',
'MM/DD': 'MM/DD',
'YY-MM': 'YY-MM',
YY: 'YY',
};
const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
(
{
@@ -80,11 +94,13 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
dragSelectColor,
},
ref,
// eslint-disable-next-line sonarjs/cognitive-complexity
): JSX.Element => {
const nearestDatasetIndex = useRef<null | number>(null);
const chartRef = useRef<HTMLCanvasElement>(null);
const isDarkMode = useIsDarkMode();
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
const { timezone } = useTimezone();
const currentTheme = isDarkMode ? 'dark' : 'light';
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
@@ -112,6 +128,22 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
return 'rgba(231,233,237,0.8)';
}, [currentTheme]);
// Override Chart.js date adapter to use dayjs with timezone support
useEffect(() => {
_adapters._date.override({
format(time: number | Date, fmt: string) {
const dayjsTime = dayjs(time).tz(timezone?.value);
const format = formatMap[fmt as keyof typeof formatMap];
if (!format) {
console.warn(`Missing datetime format for ${fmt}`);
return dayjsTime.format('YYYY-MM-DD HH:mm:ss'); // fallback format
}
return dayjsTime.format(format);
},
});
}, [timezone]);
const buildChart = useCallback(() => {
if (lineChartRef.current !== undefined) {
lineChartRef.current.destroy();
@@ -132,6 +164,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
isStacked,
onClickHandler,
data,
timezone,
);
const chartHasData = hasData(data);
@@ -166,6 +199,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
isStacked,
onClickHandler,
data,
timezone,
name,
type,
]);

View File

@@ -1,5 +1,6 @@
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import dayjs from 'dayjs';
import { MutableRefObject } from 'react';
@@ -50,6 +51,7 @@ export const getGraphOptions = (
isStacked: boolean | undefined,
onClickHandler: GraphOnClickHandler | undefined,
data: ChartData,
timezone: Timezone,
// eslint-disable-next-line sonarjs/cognitive-complexity
): CustomChartOptions => ({
animation: {
@@ -97,7 +99,7 @@ export const getGraphOptions = (
callbacks: {
title(context): string | string[] {
const date = dayjs(context[0].parsed.x);
return date.format('MMM DD, YYYY, HH:mm:ss');
return date.tz(timezone?.value).format('MMM DD, YYYY, HH:mm:ss');
},
label(context): string | string[] {
let label = context.dataset.label || '';

View File

@@ -8,13 +8,13 @@ import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants';
import { unescapeString } from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode';
// utils
import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useMemo, useState } from 'react';
// interfaces
import { IField } from 'types/api/logs/fields';
@@ -174,12 +174,20 @@ function ListLogView({
[selectedFields],
);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const timestampValue = useMemo(
() =>
typeof flattenLogData.timestamp === 'string'
? dayjs(flattenLogData.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(flattenLogData.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'),
[flattenLogData.timestamp],
? formatTimezoneAdjustedTimestamp(
flattenLogData.timestamp,
'YYYY-MM-DD HH:mm:ss.SSS',
)
: formatTimezoneAdjustedTimestamp(
flattenLogData.timestamp / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
),
[flattenLogData.timestamp, formatTimezoneAdjustedTimestamp],
);
const logType = getLogIndicatorType(logData);

View File

@@ -17,6 +17,7 @@ describe('getLogIndicatorType', () => {
body: 'Sample log Message',
resources_string: {},
attributesString: {},
scope_string: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
@@ -40,6 +41,7 @@ describe('getLogIndicatorType', () => {
body: 'Sample log Message',
resources_string: {},
attributesString: {},
scope_string: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
@@ -62,6 +64,7 @@ describe('getLogIndicatorType', () => {
body: 'Sample log Message',
resources_string: {},
attributesString: {},
scope_string: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
@@ -83,6 +86,7 @@ describe('getLogIndicatorType', () => {
body: 'Sample log',
resources_string: {},
attributesString: {},
scope_string: {},
attributes_string: {
log_level: 'INFO' as never,
},
@@ -112,6 +116,7 @@ describe('getLogIndicatorTypeForTable', () => {
attributesString: {},
attributes_string: {},
attributesInt: {},
scope_string: {},
attributesFloat: {},
severity_text: 'WARN',
};
@@ -130,6 +135,7 @@ describe('getLogIndicatorTypeForTable', () => {
severity_number: 0,
body: 'Sample log message',
resources_string: {},
scope_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
@@ -166,6 +172,7 @@ describe('logIndicatorBySeverityNumber', () => {
body: 'Sample log Message',
resources_string: {},
attributesString: {},
scope_string: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},

View File

@@ -6,7 +6,6 @@ import LogDetail from 'components/LogDetail';
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
import { unescapeString } from 'container/LogDetailedView/utils';
import LogsExplorerContext from 'container/LogsExplorerContext';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
@@ -14,6 +13,7 @@ import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData';
import { isEmpty, isNumber, isUndefined } from 'lodash-es';
import { useTimezone } from 'providers/Timezone';
import {
KeyboardEvent,
MouseEvent,
@@ -89,16 +89,24 @@ function RawLogView({
attributesText += ' | ';
}
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const text = useMemo(() => {
const date =
typeof data.timestamp === 'string'
? dayjs(data.timestamp)
: dayjs(data.timestamp / 1e6);
? formatTimezoneAdjustedTimestamp(data.timestamp, 'YYYY-MM-DD HH:mm:ss.SSS')
: formatTimezoneAdjustedTimestamp(
data.timestamp / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
);
return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${
data.body
}`;
}, [data.timestamp, data.body, attributesText]);
return `${date} | ${attributesText} ${data.body}`;
}, [
data.timestamp,
data.body,
attributesText,
formatTimezoneAdjustedTimestamp,
]);
const handleClickExpand = useCallback(() => {
if (activeContextLog || isReadOnly) return;

View File

@@ -5,10 +5,10 @@ import { Typography } from 'antd';
import { ColumnsType } from 'antd/es/table';
import cx from 'classnames';
import { unescapeString } from 'container/LogDetailedView/utils';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { useMemo } from 'react';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
@@ -44,6 +44,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
logs,
]);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
.filter((e) => e.name !== 'id')
@@ -81,8 +83,11 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
const date =
typeof field === 'string'
? dayjs(field).format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(field / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
: formatTimezoneAdjustedTimestamp(
field / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
);
return {
children: (
<div className="table-timestamp">
@@ -125,7 +130,15 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
},
...(appendTo === 'end' ? fieldColumns : []),
];
}, [fields, isListViewPanel, appendTo, isDarkMode, linesPerRow, fontSize]);
}, [
fields,
isListViewPanel,
appendTo,
isDarkMode,
linesPerRow,
fontSize,
formatTimezoneAdjustedTimestamp,
]);
return { columns, dataSource: flattenLogData };
};

View File

@@ -1,11 +1,13 @@
import { Typography } from 'antd';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
import getFormattedDate from 'lib/getFormatedDate';
import { useTimezone } from 'providers/Timezone';
function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const time = new Date(CreatedOrUpdateTime);
const date = getFormattedDate(time);
const timeString = `${date} ${convertDateToAmAndPm(time)}`;
const timeString = formatTimezoneAdjustedTimestamp(
time,
'MM/DD/YYYY hh:mm:ss A (UTC Z)',
);
return <Typography>{timeString}</Typography>;
}

View File

@@ -21,4 +21,5 @@ export enum LOCALSTORAGE {
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS',
SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS',
PREFERRED_TIMEZONE = 'PREFERRED_TIMEZONE',
}

View File

@@ -37,4 +37,8 @@ export enum QueryParams {
partition = 'partition',
selectedTimelineQuery = 'selectedTimelineQuery',
ruleType = 'ruleType',
configDetail = 'configDetail',
getStartedSource = 'getStartedSource',
getStartedSourceService = 'getStartedSourceService',
mqServiceView = 'mqServiceView',
}

View File

@@ -18,4 +18,5 @@ export const REACT_QUERY_KEY = {
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
};

View File

@@ -0,0 +1,3 @@
export const TimezonePickerShortcuts = {
CloseTimezonePicker: 'escape',
};

View File

@@ -7,6 +7,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions';
@@ -48,6 +49,7 @@ function HorizontalTimelineGraph({
const urlQuery = useUrlQuery();
const dispatch = useDispatch();
const { timezone } = useTimezone();
const options: uPlot.Options = useMemo(
() => ({
@@ -116,8 +118,18 @@ function HorizontalTimelineGraph({
}),
]
: [],
tzDate: (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
}),
[width, isDarkMode, transformedData.length, urlQuery, dispatch],
[
width,
isDarkMode,
transformedData.length,
urlQuery,
dispatch,
timezone?.value,
],
);
return <Uplot data={transformedData} options={options} />;
}

View File

@@ -6,6 +6,7 @@ import {
useGetAlertRuleDetailsTimelineTable,
useTimelineTable,
} from 'pages/AlertDetails/hooks';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
@@ -39,6 +40,8 @@ function TimelineTable(): JSX.Element {
const { t } = useTranslation('common');
const { formatTimezoneAdjustedTimestamp } = useTimezone();
if (isError || !isValidRuleId || !ruleId) {
return <div>{t('something_went_wrong')}</div>;
}
@@ -51,6 +54,7 @@ function TimelineTable(): JSX.Element {
filters,
labels: labels ?? {},
setFilters,
formatTimezoneAdjustedTimestamp,
})}
dataSource={timelineData}
pagination={paginationConfig}

View File

@@ -8,6 +8,7 @@ import ClientSideQBSearch, {
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
import { transformKeyValuesToAttributeValuesMap } from 'container/QueryBuilder/filters/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import { Search } from 'lucide-react';
import AlertLabels, {
AlertLabelsProps,
@@ -16,7 +17,6 @@ import AlertState from 'pages/AlertDetails/AlertHeader/AlertState/AlertState';
import { useMemo } from 'react';
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { formatEpochTimestamp } from 'utils/timeUtils';
const transformLabelsToQbKeys = (
labels: AlertRuleTimelineTableResponse['labels'],
@@ -74,10 +74,15 @@ export const timelineTableColumns = ({
filters,
labels,
setFilters,
formatTimezoneAdjustedTimestamp,
}: {
filters: TagFilter;
labels: AlertLabelsProps['labels'];
setFilters: (filters: TagFilter) => void;
formatTimezoneAdjustedTimestamp: (
input: TimestampInput,
format?: string,
) => string;
}): ColumnsType<AlertRuleTimelineTableResponse> => [
{
title: 'STATE',
@@ -106,7 +111,9 @@ export const timelineTableColumns = ({
dataIndex: 'unixMilli',
width: 200,
render: (value): JSX.Element => (
<div className="alert-rule__created-at">{formatEpochTimestamp(value)}</div>
<div className="alert-rule__created-at">
{formatTimezoneAdjustedTimestamp(value, 'MMM D, YYYY ⎯ HH:mm:ss')}
</div>
),
},
{

View File

@@ -17,14 +17,15 @@ import getAll from 'api/errors/getAll';
import getErrorCounts from 'api/errors/getErrorCounts';
import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
@@ -155,8 +156,16 @@ function AllErrors(): JSX.Element {
}
}, [data?.error, data?.payload, t, notifications]);
const getDateValue = (value: string): JSX.Element => (
<Typography>{dayjs(value).format('DD/MM/YYYY HH:mm:ss A')}</Typography>
const getDateValue = (
value: string,
formatTimezoneAdjustedTimestamp: (
input: TimestampInput,
format?: string,
) => string,
): JSX.Element => (
<Typography>
{formatTimezoneAdjustedTimestamp(value, 'DD/MM/YYYY hh:mm:ss A')}
</Typography>
);
const filterIcon = useCallback(() => <SearchOutlined />, []);
@@ -283,6 +292,8 @@ function AllErrors(): JSX.Element {
[filterIcon, filterDropdownWrapper],
);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns: ColumnsType<Exception> = [
{
title: 'Exception Type',
@@ -342,7 +353,8 @@ function AllErrors(): JSX.Element {
dataIndex: 'lastSeen',
width: 80,
key: 'lastSeen',
render: getDateValue,
render: (value): JSX.Element =>
getDateValue(value, formatTimezoneAdjustedTimestamp),
sorter: true,
defaultSortOrder: getDefaultOrder(
getUpdatedParams,
@@ -355,7 +367,8 @@ function AllErrors(): JSX.Element {
dataIndex: 'firstSeen',
width: 80,
key: 'firstSeen',
render: getDateValue,
render: (value): JSX.Element =>
getDateValue(value, formatTimezoneAdjustedTimestamp),
sorter: true,
defaultSortOrder: getDefaultOrder(
getUpdatedParams,

View File

@@ -10,6 +10,7 @@ import getAxes from 'lib/uPlotLib/utils/getAxes';
import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData';
import { getYAxisScaleForAnomalyDetection } from 'lib/uPlotLib/utils/getYAxisScale';
import { LineChart } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { useEffect, useRef, useState } from 'react';
import uPlot from 'uplot';
@@ -148,10 +149,12 @@ function AnomalyAlertEvaluationView({
]
: [];
const { timezone } = useTimezone();
const options = {
width: dimensions.width,
height: dimensions.height - 36,
plugins: [bandsPlugin, tooltipPlugin(isDarkMode)],
plugins: [bandsPlugin, tooltipPlugin(isDarkMode, timezone?.value)],
focus: {
alpha: 0.3,
},
@@ -256,6 +259,8 @@ function AnomalyAlertEvaluationView({
show: true,
},
axes: getAxes(isDarkMode, yAxisUnit),
tzDate: (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
};
const handleSearch = (searchText: string): void => {

View File

@@ -1,8 +1,10 @@
import { themeColors } from 'constants/theme';
import dayjs from 'dayjs';
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
const tooltipPlugin = (
isDarkMode: boolean,
timezone: string,
): { hooks: { init: (u: any) => void } } => {
let tooltip: HTMLDivElement;
const tooltipLeftOffset = 10;
@@ -17,7 +19,7 @@ const tooltipPlugin = (
return value.toFixed(3);
}
if (value instanceof Date) {
return value.toLocaleString();
return dayjs(value).tz(timezone).format('MM/DD/YYYY, h:mm:ss A');
}
if (value == null) {
return 'N/A';

View File

@@ -192,7 +192,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const renderFullScreen =
pathname === ROUTES.GET_STARTED ||
pathname === ROUTES.ONBOARDING ||
pathname === ROUTES.WORKSPACE_LOCKED ||
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||

View File

@@ -57,6 +57,7 @@ export const alertDefaults: AlertDef = {
},
annotations: defaultAnnotations,
evalWindow: defaultEvalWindow,
alert: '',
};
export const anamolyAlertDefaults: AlertDef = {
@@ -94,12 +95,14 @@ export const anamolyAlertDefaults: AlertDef = {
matchType: defaultMatchType,
algorithm: defaultAlgorithm,
seasonality: defaultSeasonality,
target: 3,
},
labels: {
severity: 'warning',
},
annotations: defaultAnnotations,
evalWindow: defaultEvalWindow,
alert: '',
};
export const logAlertDefaults: AlertDef = {
@@ -131,6 +134,7 @@ export const logAlertDefaults: AlertDef = {
},
annotations: defaultAnnotations,
evalWindow: defaultEvalWindow,
alert: '',
};
export const traceAlertDefaults: AlertDef = {
@@ -162,6 +166,7 @@ export const traceAlertDefaults: AlertDef = {
},
annotations: defaultAnnotations,
evalWindow: defaultEvalWindow,
alert: '',
};
export const exceptionAlertDefaults: AlertDef = {
@@ -193,6 +198,7 @@ export const exceptionAlertDefaults: AlertDef = {
},
annotations: defaultAnnotations,
evalWindow: defaultEvalWindow,
alert: '',
};
export const ALERTS_VALUES_MAP: Record<AlertTypes, AlertDef> = {

View File

@@ -6,12 +6,12 @@ import getNextPrevId from 'api/errors/getNextPrevId';
import Editor from 'components/Editor';
import { ResizeTable } from 'components/ResizeTable';
import { getNanoSeconds } from 'container/AllError/utils';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { urlKey } from 'pages/ErrorDetails/utils';
import { useTimezone } from 'providers/Timezone';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
@@ -103,8 +103,6 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
}
};
const timeStamp = dayjs(errorDetail.timestamp);
const data: { key: string; value: string }[] = Object.keys(errorDetail)
.filter((e) => !keyToExclude.includes(e))
.map((key) => ({
@@ -136,6 +134,8 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
return (
<>
<Typography>{errorDetail.exceptionType}</Typography>
@@ -145,7 +145,12 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
<EventContainer>
<div>
<Typography>Event {errorDetail.errorId}</Typography>
<Typography>{timeStamp.format('MMM DD YYYY hh:mm:ss A')}</Typography>
<Typography>
{formatTimezoneAdjustedTimestamp(
errorDetail.timestamp,
'DD/MM/YYYY hh:mm:ss A (UTC Z)',
)}
</Typography>
</div>
<div>
<Space align="end" direction="horizontal">

View File

@@ -1,4 +1,4 @@
import { Color } from '@signozhq/design-tokens';
import { Color, ColorType } from '@signozhq/design-tokens';
import { showErrorNotification } from 'components/ExplorerCard/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query';
@@ -8,7 +8,7 @@ import { DataSource } from 'types/common/queryBuilder';
import { SaveNewViewHandlerProps } from './types';
export const getRandomColor = (): Color => {
export const getRandomColor = (): ColorType => {
const colorKeys = Object.keys(Color) as (keyof typeof Color)[];
const randomKey = colorKeys[Math.floor(Math.random() * colorKeys.length)];
return Color[randomKey];

View File

@@ -8,7 +8,7 @@ import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -83,16 +83,22 @@ function BasicInfo({
window.open(ROUTES.CHANNELS_NEW, '_blank');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const hasLoggedEvent = useRef(false);
useEffect(() => {
if (!channels.loading && isNewRule) {
if (!channels.loading && isNewRule && !hasLoggedEvent.current) {
logEvent('Alert: New alert creation page visited', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
numberOfChannels: channels?.payload?.length,
});
hasLoggedEvent.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channels.payload, channels.loading]);
}, [channels.loading]);
const refetchChannels = async (): Promise<void> => {
await channels.refetch();
};
return (
<>
@@ -197,7 +203,7 @@ function BasicInfo({
{!shouldBroadCastToAllChannels && (
<Tooltip
title={
noChannels
noChannels && !addNewChannelPermission
? 'No channels. Ask an admin to create a notification channel'
: undefined
}
@@ -212,10 +218,10 @@ function BasicInfo({
]}
>
<ChannelSelect
disabled={
shouldBroadCastToAllChannels || noChannels || !!channels.loading
}
onDropdownOpen={refetchChannels}
disabled={shouldBroadCastToAllChannels}
currentValue={alertDef.preferredChannels}
handleCreateNewChannels={handleCreateNewChannels}
channels={channels}
onSelectChannels={(preferredChannels): void => {
setAlertDef({

View File

@@ -1,24 +1,33 @@
import { Select } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { Select, Spin } from 'antd';
import useComponentPermission from 'hooks/useComponentPermission';
import { State } from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { PayloadProps } from 'types/api/channels/getAll';
import AppReducer from 'types/reducer/app';
import { StyledSelect } from './styles';
import { StyledCreateChannelOption, StyledSelect } from './styles';
export interface ChannelSelectProps {
disabled?: boolean;
currentValue?: string[];
onSelectChannels: (s: string[]) => void;
onDropdownOpen: () => void;
channels: State<PayloadProps | undefined>;
handleCreateNewChannels: () => void;
}
function ChannelSelect({
disabled,
currentValue,
onSelectChannels,
onDropdownOpen,
channels,
handleCreateNewChannels,
}: ChannelSelectProps): JSX.Element | null {
// init namespace for translations
const { t } = useTranslation('alerts');
@@ -26,6 +35,10 @@ function ChannelSelect({
const { notifications } = useNotifications();
const handleChange = (value: string[]): void => {
if (value.includes('add-new-channel')) {
handleCreateNewChannels();
return;
}
onSelectChannels(value);
};
@@ -35,9 +48,27 @@ function ChannelSelect({
description: channels.errorMessage,
});
}
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const [addNewChannelPermission] = useComponentPermission(
['add_new_channel'],
role,
);
const renderOptions = (): ReactNode[] => {
const children: ReactNode[] = [];
if (!channels.loading && addNewChannelPermission) {
children.push(
<Select.Option key="add-new-channel" value="add-new-channel">
<StyledCreateChannelOption>
<PlusOutlined />
Create a new channel
</StyledCreateChannelOption>
</Select.Option>,
);
}
if (
channels.loading ||
channels.payload === undefined ||
@@ -56,6 +87,7 @@ function ChannelSelect({
return children;
};
return (
<StyledSelect
disabled={disabled}
@@ -65,6 +97,12 @@ function ChannelSelect({
placeholder={t('placeholder_channel_select')}
data-testid="alert-channel-select"
value={currentValue}
notFoundContent={channels.loading && <Spin size="small" />}
onDropdownVisibleChange={(open): void => {
if (open) {
onDropdownOpen();
}
}}
onChange={(value): void => {
handleChange(value as string[]);
}}

View File

@@ -4,3 +4,10 @@ import styled from 'styled-components';
export const StyledSelect = styled(Select)`
border-radius: 4px;
`;
export const StyledCreateChannelOption = styled.div`
color: var(--bg-robin-500);
display: flex;
align-items: center;
gap: 8px;
`;

View File

@@ -25,6 +25,7 @@ import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@@ -35,6 +36,7 @@ import { AlertDef } from 'types/api/alerts/def';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
import uPlot from 'uplot';
import { getGraphType } from 'utils/getGraphType';
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
import { getTimeRange } from 'utils/getTimeRange';
@@ -201,6 +203,8 @@ function ChartPreview({
[dispatch, location.pathname, urlQuery],
);
const { timezone } = useTimezone();
const options = useMemo(
() =>
getUPlotChartOptions({
@@ -236,6 +240,9 @@ function ChartPreview({
softMax: null,
softMin: null,
panelType: graphType,
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
timezone: timezone?.value,
}),
[
yAxisUnit,
@@ -250,6 +257,7 @@ function ChartPreview({
optionName,
alertDef?.condition.targetUnit,
graphType,
timezone?.value,
],
);

View File

@@ -102,9 +102,9 @@ function RuleOptions({
<Select.Option value="4">{t('option_notequal')}</Select.Option>
</>
)}
{/* the value 5 and 6 are reserved for above or equal and below or equal */}
{ruleType === 'anomaly_rule' && (
<Select.Option value="5">{t('option_above_below')}</Select.Option>
<Select.Option value="7">{t('option_above_below')}</Select.Option>
)}
</InlineSelect>
);
@@ -386,32 +386,31 @@ function RuleOptions({
renderThresholdRuleOpts()}
<Space direction="vertical" size="large">
{queryCategory !== EQueryType.PROM &&
ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
<Space direction="horizontal" align="center">
<Form.Item noStyle name={['condition', 'target']}>
<InputNumber
addonBefore={t('field_threshold')}
value={alertDef?.condition?.target}
onChange={onChange}
type="number"
onWheel={(e): void => e.currentTarget.blur()}
/>
</Form.Item>
{ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
<Space direction="horizontal" align="center">
<Form.Item noStyle name={['condition', 'target']}>
<InputNumber
addonBefore={t('field_threshold')}
value={alertDef?.condition?.target}
onChange={onChange}
type="number"
onWheel={(e): void => e.currentTarget.blur()}
/>
</Form.Item>
<Form.Item noStyle>
<Select
getPopupContainer={popupContainer}
allowClear
showSearch
options={categorySelectOptions}
placeholder={t('field_unit')}
value={alertDef.condition.targetUnit}
onChange={onChangeAlertUnit}
/>
</Form.Item>
</Space>
)}
<Form.Item noStyle>
<Select
getPopupContainer={popupContainer}
allowClear
showSearch
options={categorySelectOptions}
placeholder={t('field_unit')}
value={alertDef.condition.targetUnit}
onChange={onChangeAlertUnit}
/>
</Form.Item>
</Space>
)}
<Collapse>
<Collapse.Panel header={t('More options')} key="1">

View File

@@ -53,6 +53,7 @@ import {
QueryFunctionProps,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import BasicInfo from './BasicInfo';
@@ -73,6 +74,19 @@ export enum AlertDetectionTypes {
ANOMALY_DETECTION_ALERT = 'anomaly_rule',
}
const ALERT_SETUP_GUIDE_URLS: Record<AlertTypes, string> = {
[AlertTypes.METRICS_BASED_ALERT]:
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
[AlertTypes.LOGS_BASED_ALERT]:
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
[AlertTypes.TRACES_BASED_ALERT]:
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
[AlertTypes.EXCEPTIONS_BASED_ALERT]:
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
[AlertTypes.ANOMALY_BASED_ALERT]:
'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
};
// eslint-disable-next-line sonarjs/cognitive-complexity
function FormAlertRules({
alertType,
@@ -92,6 +106,11 @@ function FormAlertRules({
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const dataSource = useMemo(
() => urlQuery.get(QueryParams.alertType) as DataSource,
[urlQuery],
);
// In case of alert the panel types should always be "Graph" only
const panelType = PANEL_TYPES.TIME_SERIES;
@@ -101,13 +120,12 @@ function FormAlertRules({
handleSetQueryData,
handleRunQuery,
handleSetConfig,
initialDataSource,
redirectWithQueryBuilderData,
} = useQueryBuilder();
useEffect(() => {
handleSetConfig(panelType || PANEL_TYPES.TIME_SERIES, initialDataSource);
}, [handleSetConfig, initialDataSource, panelType]);
handleSetConfig(panelType || PANEL_TYPES.TIME_SERIES, dataSource);
}, [handleSetConfig, dataSource, panelType]);
// use query client
const ruleCache = useQueryClient();
@@ -702,6 +720,29 @@ function FormAlertRules({
const isRuleCreated = !ruleId || ruleId === 0;
function handleRedirection(option: AlertTypes): void {
let url;
if (
option === AlertTypes.METRICS_BASED_ALERT &&
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
) {
url = ALERT_SETUP_GUIDE_URLS[AlertTypes.ANOMALY_BASED_ALERT];
} else {
url = ALERT_SETUP_GUIDE_URLS[option];
}
if (url) {
logEvent('Alert: Check example alert clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: currentQuery.queryType,
link: url,
});
window.open(url, '_blank');
}
}
useEffect(() => {
if (!isRuleCreated) {
logEvent('Alert: Edit page visited', {
@@ -752,7 +793,11 @@ function FormAlertRules({
)}
</div>
<Button className="periscope-btn" icon={<ExternalLink size={14} />}>
<Button
className="periscope-btn"
onClick={(): void => handleRedirection(alertDef.alertType as AlertTypes)}
icon={<ExternalLink size={14} />}
>
Alert Setup Guide
</Button>
</div>

View File

@@ -138,6 +138,9 @@ function LabelSelect({
if (e.key === 'Enter' || e.code === 'Enter' || e.key === ':') {
send('NEXT');
}
if (state.value === 'Idle') {
send('NEXT');
}
}}
bordered={false}
value={currentVal as never}

View File

@@ -4,6 +4,7 @@ import { Popover, Typography } from 'antd';
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useTimezone } from 'providers/Timezone';
import { useEffect } from 'react';
import { toFixed } from 'utils/toFixed';
@@ -32,13 +33,17 @@ function Span(props: SpanLengthProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const { time, timeUnitName } = convertTimeToRelevantUnit(inMsCount);
const { timezone } = useTimezone();
useEffect(() => {
document.documentElement.scrollTop = document.documentElement.clientHeight;
document.documentElement.scrollLeft = document.documentElement.clientWidth;
}, []);
const getContent = (): JSX.Element => {
const timeStamp = dayjs(startTime).format('h:mm:ss:SSS A');
const timeStamp = dayjs(startTime)
.tz(timezone.value)
.format('h:mm:ss:SSS A (UTC Z)');
const startTimeInMs = startTime - globalStart;
return (
<div>

View File

@@ -18,8 +18,13 @@
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 28px;
/* 155.556% */
letter-spacing: -0.09px;
width: 72%; // arbitrary number to match input width
display: flex;
align-items: center;
gap: 8px;
justify-content: space-between;
}
.subtitle {
@@ -356,6 +361,8 @@
flex: 1;
.heading {
margin-bottom: 8px;
.title {
font-size: 12px;
}
@@ -370,6 +377,18 @@
.ant-input-number {
width: 80%;
}
.no-limit {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 24px;
font-weight: 700;
font-size: 12px;
color: var(--bg-forest-400);
}
}
.signal-limit-view-mode {

View File

@@ -12,6 +12,7 @@ import {
Modal,
Row,
Select,
Switch,
Table,
TablePaginationConfig,
TableProps as AntDTableProps,
@@ -30,11 +31,11 @@ import { AxiosError } from 'axios';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import Tags from 'components/Tags/Tags';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import dayjs, { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useNotifications } from 'hooks/useNotifications';
import { isNil } from 'lodash-es';
import { isNil, isUndefined } from 'lodash-es';
import {
ArrowUpRight,
CalendarClock,
@@ -50,6 +51,7 @@ import {
Trash2,
X,
} from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { ChangeEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query';
@@ -69,7 +71,10 @@ const { Option } = Select;
const BYTES = 1073741824;
export const disabledDate = (current: Dayjs): boolean =>
// Using any type here because antd's DatePicker expects its own internal Dayjs type
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export const disabledDate = (current: any): boolean =>
// Disable all dates before today
current && current < dayjs().endOf('day');
@@ -392,86 +397,11 @@ function MultiIngestionSettings(): JSX.Element {
const gbToBytes = (gb: number): number => Math.round(gb * 1024 ** 3);
const getFormattedTime = (date: string): string =>
dayjs(date).format('MMM DD,YYYY, hh:mm a');
const handleAddLimit = (
APIKey: IngestionKeyProps,
signalName: string,
): void => {
setActiveSignal({
id: signalName,
signal: signalName,
config: {},
});
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue();
const payload = {
keyID: APIKey.id,
signal: signalName,
config: {
day: {
size: gbToBytes(dailyLimit),
},
second: {
size: gbToBytes(secondsLimit),
},
},
};
createLimitForIngestionKey(payload);
};
const handleUpdateLimit = (
APIKey: IngestionKeyProps,
signal: LimitProps,
): void => {
setActiveSignal(signal);
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue();
const payload = {
limitID: signal.id,
signal: signal.signal,
config: {
day: {
size: gbToBytes(dailyLimit),
},
second: {
size: gbToBytes(secondsLimit),
},
},
};
updateLimitForIngestionKey(payload);
};
const bytesToGb = (size: number | undefined): number => {
if (!size) {
return 0;
}
return size / BYTES;
};
const enableEditLimitMode = (
APIKey: IngestionKeyProps,
signal: LimitProps,
): void => {
setActiveAPIKey(APIKey);
setActiveSignal(signal);
addEditLimitForm.setFieldsValue({
dailyLimit: bytesToGb(signal?.config?.day?.size || 0),
secondsLimit: bytesToGb(signal?.config?.second?.size || 0),
});
setIsEditAddLimitOpen(true);
};
const onDeleteLimitHandler = (): void => {
if (activeSignal && activeSignal?.id) {
deleteLimitForKey(activeSignal.id);
}
};
const getFormattedTime = (
date: string,
formatTimezoneAdjustedTimestamp: (date: string, format: string) => string,
): string =>
formatTimezoneAdjustedTimestamp(date, 'MMM DD,YYYY, hh:mm a (UTC Z)');
const showDeleteLimitModal = (
APIKey: IngestionKeyProps,
@@ -496,17 +426,152 @@ function MultiIngestionSettings(): JSX.Element {
addEditLimitForm.resetFields();
};
const handleAddLimit = (
APIKey: IngestionKeyProps,
signalName: string,
): void => {
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue();
const payload = {
keyID: APIKey.id,
signal: signalName,
config: {},
};
if (!isUndefined(dailyLimit)) {
payload.config = {
day: {
size: gbToBytes(dailyLimit),
},
};
}
if (!isUndefined(secondsLimit)) {
payload.config = {
...payload.config,
second: {
size: gbToBytes(secondsLimit),
},
};
}
if (isUndefined(dailyLimit) && isUndefined(secondsLimit)) {
// No need to save as no limit is provided, close the edit view and reset active signal and api key
setActiveSignal(null);
setActiveAPIKey(null);
setIsEditAddLimitOpen(false);
setUpdatedTags([]);
hideAddViewModal();
setHasCreateLimitForIngestionKeyError(false);
return;
}
createLimitForIngestionKey(payload);
};
const handleUpdateLimit = (
APIKey: IngestionKeyProps,
signal: LimitProps,
): void => {
const { dailyLimit, secondsLimit } = addEditLimitForm.getFieldsValue();
const payload = {
limitID: signal.id,
signal: signal.signal,
config: {},
};
if (isUndefined(dailyLimit) && isUndefined(secondsLimit)) {
showDeleteLimitModal(APIKey, signal);
return;
}
if (!isUndefined(dailyLimit)) {
payload.config = {
day: {
size: gbToBytes(dailyLimit),
},
};
}
if (!isUndefined(secondsLimit)) {
payload.config = {
...payload.config,
second: {
size: gbToBytes(secondsLimit),
},
};
}
updateLimitForIngestionKey(payload);
};
const bytesToGb = (size: number | undefined): number => {
if (!size) {
return 0;
}
return size / BYTES;
};
const enableEditLimitMode = (
APIKey: IngestionKeyProps,
signal: LimitProps,
): void => {
setActiveAPIKey(APIKey);
setActiveSignal({
...signal,
config: {
...signal.config,
day: {
...signal.config?.day,
enabled: !isNil(signal?.config?.day?.size),
},
second: {
...signal.config?.second,
enabled: !isNil(signal?.config?.second?.size),
},
},
});
addEditLimitForm.setFieldsValue({
dailyLimit: bytesToGb(signal?.config?.day?.size || 0),
secondsLimit: bytesToGb(signal?.config?.second?.size || 0),
enableDailyLimit: !isNil(signal?.config?.day?.size),
enableSecondLimit: !isNil(signal?.config?.second?.size),
});
setIsEditAddLimitOpen(true);
};
const onDeleteLimitHandler = (): void => {
if (activeSignal && activeSignal?.id) {
deleteLimitForKey(activeSignal.id);
}
};
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns: AntDTableProps<IngestionKeyProps>['columns'] = [
{
title: 'Ingestion Key',
key: 'ingestion-key',
// eslint-disable-next-line sonarjs/cognitive-complexity
render: (APIKey: IngestionKeyProps): JSX.Element => {
const createdOn = getFormattedTime(APIKey.created_at);
const createdOn = getFormattedTime(
APIKey.created_at,
formatTimezoneAdjustedTimestamp,
);
const formattedDateAndTime =
APIKey && APIKey?.expires_at && getFormattedTime(APIKey?.expires_at);
APIKey &&
APIKey?.expires_at &&
getFormattedTime(APIKey?.expires_at, formatTimezoneAdjustedTimestamp);
const updatedOn = getFormattedTime(APIKey?.updated_at);
const updatedOn = getFormattedTime(
APIKey?.updated_at,
formatTimezoneAdjustedTimestamp,
);
const limits: { [key: string]: LimitProps } = {};
@@ -684,50 +749,108 @@ function MultiIngestionSettings(): JSX.Element {
<div className="signal-limit-edit-mode">
<div className="daily-limit">
<div className="heading">
<div className="title"> Daily limit </div>
<div className="title">
Daily limit
<div className="limit-enable-disable-toggle">
<Form.Item name="enableDailyLimit">
<Switch
size="small"
checked={activeSignal?.config?.day?.enabled}
onChange={(value): void => {
setActiveSignal({
...activeSignal,
config: {
...activeSignal.config,
day: {
...activeSignal.config?.day,
enabled: value,
},
},
});
}}
/>
</Form.Item>
</div>
</div>
<div className="subtitle">
Add a limit for data ingested daily{' '}
Add a limit for data ingested daily
</div>
</div>
<div className="size">
<Form.Item name="dailyLimit">
<InputNumber
addonAfter={
<Select defaultValue="GiB" disabled>
<Option value="TiB"> TiB</Option>
<Option value="GiB"> GiB</Option>
<Option value="MiB"> MiB </Option>
<Option value="KiB"> KiB </Option>
</Select>
}
/>
</Form.Item>
{activeSignal?.config?.day?.enabled ? (
<Form.Item name="dailyLimit" key="dailyLimit">
<InputNumber
disabled={!activeSignal?.config?.day?.enabled}
key="dailyLimit"
addonAfter={
<Select defaultValue="GiB" disabled>
<Option value="TiB"> TiB</Option>
<Option value="GiB"> GiB</Option>
<Option value="MiB"> MiB </Option>
<Option value="KiB"> KiB </Option>
</Select>
}
/>
</Form.Item>
) : (
<div className="no-limit">
<Infinity size={16} /> NO LIMIT
</div>
)}
</div>
</div>
<div className="second-limit">
<div className="heading">
<div className="title"> Per Second limit </div>
<div className="title">
Per Second limit{' '}
<div className="limit-enable-disable-toggle">
<Form.Item name="enableSecondLimit">
<Switch
size="small"
checked={activeSignal?.config?.second?.enabled}
onChange={(value): void => {
setActiveSignal({
...activeSignal,
config: {
...activeSignal.config,
second: {
...activeSignal.config?.second,
enabled: value,
},
},
});
}}
/>
</Form.Item>
</div>
</div>
<div className="subtitle">
{' '}
Add a limit for data ingested every second{' '}
Add a limit for data ingested every second
</div>
</div>
<div className="size">
<Form.Item name="secondsLimit">
<InputNumber
addonAfter={
<Select defaultValue="GiB" disabled>
<Option value="TiB"> TiB</Option>
<Option value="GiB"> GiB</Option>
<Option value="MiB"> MiB </Option>
<Option value="KiB"> KiB </Option>
</Select>
}
/>
</Form.Item>
{activeSignal?.config?.second?.enabled ? (
<Form.Item name="secondsLimit" key="secondsLimit">
<InputNumber
key="secondsLimit"
disabled={!activeSignal?.config?.second?.enabled}
addonAfter={
<Select defaultValue="GiB" disabled>
<Option value="TiB"> TiB</Option>
<Option value="GiB"> GiB</Option>
<Option value="MiB"> MiB </Option>
<Option value="KiB"> KiB </Option>
</Select>
}
/>
</Form.Item>
) : (
<div className="no-limit">
<Infinity size={16} /> NO LIMIT
</div>
)}
</div>
</div>
</div>

View File

@@ -1,8 +1,20 @@
import { Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
import { useTimezone } from 'providers/Timezone';
import { useTranslation } from 'react-i18next';
import { License } from 'types/api/licenses/def';
function ValidityColumn({ value }: { value: string }): JSX.Element {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
return (
<Typography>
{formatTimezoneAdjustedTimestamp(value, 'YYYY-MM-DD HH:mm:ss (UTC Z)')}
</Typography>
);
}
function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
const { t } = useTranslation(['licenses']);
@@ -23,12 +35,14 @@ function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
title: t('column_valid_from'),
dataIndex: 'ValidFrom',
key: 'valid from',
render: (value: string): JSX.Element => ValidityColumn({ value }),
width: 80,
},
{
title: t('column_valid_until'),
dataIndex: 'ValidUntil',
key: 'valid until',
render: (value: string): JSX.Element => ValidityColumn({ value }),
width: 80,
},
];

View File

@@ -57,6 +57,7 @@ import {
// see more: https://github.com/lucide-icons/lucide/issues/94
import { handleContactSupport } from 'pages/Integrations/utils';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone';
import {
ChangeEvent,
Key,
@@ -343,31 +344,13 @@ function DashboardsList(): JSX.Element {
}
}, [state.error, state.value, t, notifications]);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
function getFormattedTime(dashboard: Dashboard, option: string): string {
const timeOptions: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
};
const formattedTime = new Date(get(dashboard, option, '')).toLocaleTimeString(
'en-US',
timeOptions,
return formatTimezoneAdjustedTimestamp(
get(dashboard, option, ''),
'MMM D, YYYY ⎯ HH:mm:ss',
);
const dateOptions: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
};
const formattedDate = new Date(get(dashboard, option, '')).toLocaleDateString(
'en-US',
dateOptions,
);
// Combine time and date
return `${formattedDate}${formattedTime}`;
}
const onLastUpdated = (time: string): string => {
@@ -410,31 +393,11 @@ function DashboardsList(): JSX.Element {
title: 'Dashboards',
key: 'dashboard',
render: (dashboard: Data, _, index): JSX.Element => {
const timeOptions: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
};
const formattedTime = new Date(dashboard.createdAt).toLocaleTimeString(
'en-US',
timeOptions,
const formattedDateAndTime = formatTimezoneAdjustedTimestamp(
dashboard.createdAt,
'MMM D, YYYY ⎯ HH:mm:ss',
);
const dateOptions: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
};
const formattedDate = new Date(dashboard.createdAt).toLocaleDateString(
'en-US',
dateOptions,
);
// Combine time and date
const formattedDateAndTime = `${formattedDate}${formattedTime}`;
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.id}`;
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {

View File

@@ -82,9 +82,8 @@ function ImportJSON({
const dashboardData = JSON.parse(editorValue) as DashboardData;
// Add validation for uuid
if (dashboardData.uuid !== undefined && dashboardData.uuid.trim() === '') {
// silently remove uuid if it is empty
// Remove uuid from the dashboard data, in all cases - empty, duplicate or any valid not duplicate uuid
if (dashboardData.uuid !== undefined) {
delete dashboardData.uuid;
}

View File

@@ -8,10 +8,12 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useRef } from 'react';
import { useQueries, UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import {
getHostQueryPayload,
@@ -73,6 +75,8 @@ function NodeMetrics({
[queries],
);
const { timezone } = useTimezone();
const options = useMemo(
() =>
queries.map(({ data }, idx) =>
@@ -86,6 +90,9 @@ function NodeMetrics({
minTimeScale: start,
maxTimeScale: end,
verticalLineTimestamp,
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
timezone: timezone?.value,
}),
),
[
@@ -96,6 +103,7 @@ function NodeMetrics({
start,
verticalLineTimestamp,
end,
timezone?.value,
],
);

View File

@@ -8,10 +8,12 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useRef } from 'react';
import { useQueries, UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { getPodQueryPayload, podWidgetInfo } from './constants';
@@ -60,6 +62,7 @@ function PodMetrics({
() => queries.map(({ data }) => getUPlotChartData(data?.payload)),
[queries],
);
const { timezone } = useTimezone();
const options = useMemo(
() =>
@@ -74,9 +77,20 @@ function PodMetrics({
minTimeScale: start,
maxTimeScale: end,
verticalLineTimestamp,
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
timezone: timezone?.value,
}),
),
[queries, isDarkMode, dimensions, start, verticalLineTimestamp, end],
[
queries,
isDarkMode,
dimensions,
start,
end,
verticalLineTimestamp,
timezone?.value,
],
);
const renderCardContent = (

View File

@@ -11,7 +11,8 @@ import ROUTES from 'constants/routes';
import dompurify from 'dompurify';
import { isEmpty } from 'lodash-es';
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
import { useMemo, useState } from 'react';
import { useTimezone } from 'providers/Timezone';
import React, { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
@@ -68,6 +69,8 @@ export function TableViewActions(
const [isOpen, setIsOpen] = useState<boolean>(false);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
if (record.field === 'body') {
const parsedBody = recursiveParseJSON(fieldData.value);
if (!isEmpty(parsedBody)) {
@@ -100,33 +103,44 @@ export function TableViewActions(
);
}
let cleanTimestamp: string;
if (record.field === 'timestamp') {
cleanTimestamp = fieldData.value.replace(/^["']|["']$/g, '');
}
const renderFieldContent = (): JSX.Element => {
const commonStyles: React.CSSProperties = {
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
tabSize: 4,
};
switch (record.field) {
case 'body':
return <span style={commonStyles} dangerouslySetInnerHTML={bodyHtml} />;
case 'timestamp':
return (
<span style={commonStyles}>
{formatTimezoneAdjustedTimestamp(
cleanTimestamp,
'MM/DD/YYYY, HH:mm:ss.SSS (UTC Z)',
)}
</span>
);
default:
return (
<span style={commonStyles}>{removeEscapeCharacters(fieldData.value)}</span>
);
}
};
return (
<div className={cx('value-field', isOpen ? 'open-popover' : '')}>
{record.field === 'body' ? (
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
<span
style={{
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
tabSize: 4,
}}
dangerouslySetInnerHTML={bodyHtml}
/>
</CopyClipboardHOC>
) : (
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
<span
style={{
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
tabSize: 4,
}}
>
{removeEscapeCharacters(fieldData.value)}
</span>
</CopyClipboardHOC>
)}
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
{renderFieldContent()}
</CopyClipboardHOC>
{!isListViewPanel && (
<span className="action-btn">
<Tooltip title="Filter for value">

View File

@@ -157,6 +157,11 @@ export const getFieldAttributes = (field: string): IFieldAttributes => {
const stringWithoutPrefix = field.slice('resources_'.length);
const parts = splitOnce(stringWithoutPrefix, '.');
[dataType, newField] = parts;
} else if (field.startsWith('scope_string')) {
logType = MetricsType.Scope;
const stringWithoutPrefix = field.slice('scope_'.length);
const parts = splitOnce(stringWithoutPrefix, '.');
[dataType, newField] = parts;
}
return { dataType, newField, logType };
@@ -187,6 +192,7 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => {
traceId: logData.traceId,
attributes: {},
resources: {},
scope: {},
severity_text: logData.severity_text,
severity_number: logData.severity_number,
};
@@ -198,6 +204,9 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => {
} else if (key.startsWith('resources_')) {
outputJson.resources = outputJson.resources || {};
Object.assign(outputJson.resources, logData[key as keyof ILog]);
} else if (key.startsWith('scope_string')) {
outputJson.scope = outputJson.scope || {};
Object.assign(outputJson.scope, logData[key as keyof ILog]);
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

View File

@@ -202,6 +202,7 @@ function LogsExplorerViews({
id: 'severity_text--string----true',
},
],
legend: '{{severity_text}}',
};
const modifiedQuery: Query = {

View File

@@ -15,6 +15,7 @@ import { useLogsData } from 'hooks/useLogsData';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { FlatLogData } from 'lib/logs/flatLogData';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { useTimezone } from 'providers/Timezone';
import {
Dispatch,
HTMLAttributes,
@@ -76,7 +77,12 @@ function LogsPanelComponent({
});
};
const columns = getLogPanelColumnsList(widget.selectedLogFields);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns = getLogPanelColumnsList(
widget.selectedLogFields,
formatTimezoneAdjustedTimestamp,
);
const dataLength =
queryResponse.data?.payload?.data?.newResult?.data?.result[0]?.list?.length;

View File

@@ -1,6 +1,7 @@
import { ColumnsType } from 'antd/es/table';
import { Typography } from 'antd/lib';
import { OPERATORS } from 'constants/queryBuilder';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
// import Typography from 'antd/es/typography/Typography';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { ReactNode } from 'react';
@@ -13,18 +14,31 @@ import { v4 as uuid } from 'uuid';
export const getLogPanelColumnsList = (
selectedLogFields: Widgets['selectedLogFields'],
formatTimezoneAdjustedTimestamp: (
input: TimestampInput,
format?: string,
) => string,
): ColumnsType<RowData> => {
const initialColumns: ColumnsType<RowData> = [];
const columns: ColumnsType<RowData> =
selectedLogFields?.map((field: IField) => {
const { name } = field;
return {
title: name,
dataIndex: name,
key: name,
width: name === 'body' ? 350 : 100,
render: (value: ReactNode): JSX.Element => {
if (name === 'timestamp') {
return (
<Typography.Text>
{formatTimezoneAdjustedTimestamp(value as string)}
</Typography.Text>
);
}
if (name === 'body') {
return (
<Typography.Paragraph ellipsis={{ rows: 1 }} data-testid={name}>

View File

@@ -58,12 +58,17 @@ export const databaseCallsRPS = ({
const legends = [legend];
const dataSource = DataSource.METRICS;
const timeAggregateOperators = [MetricAggregateOperator.RATE];
const spaceAggregateOperators = [MetricAggregateOperator.SUM];
return getQueryBuilderQueries({
autocompleteData,
groupBy,
legends,
filterItems,
dataSource,
timeAggregateOperators,
spaceAggregateOperators,
});
};

View File

@@ -213,12 +213,17 @@ export const externalCallRpsByAddress = ({
const legends = [legend];
const dataSource = DataSource.METRICS;
const timeAggregateOperators = [MetricAggregateOperator.RATE];
const spaceAggregateOperators = [MetricAggregateOperator.SUM];
return getQueryBuilderQueries({
autocompleteData,
groupBy,
legends,
filterItems,
dataSource,
timeAggregateOperators,
spaceAggregateOperators,
});
};

View File

@@ -25,6 +25,8 @@ export const getQueryBuilderQueries = ({
aggregateOperator,
dataSource,
queryNameAndExpression,
timeAggregateOperators,
spaceAggregateOperators,
}: BuilderQueriesProps): QueryBuilderData => ({
queryFormulas: [],
queryData: autocompleteData.map((item, index) => {
@@ -50,6 +52,8 @@ export const getQueryBuilderQueries = ({
op: 'AND',
},
reduceTo: 'avg',
spaceAggregation: spaceAggregateOperators[index],
timeAggregation: timeAggregateOperators[index],
dataSource,
};

View File

@@ -83,6 +83,17 @@ export const latency = ({
const dataSource = isSpanMetricEnable ? DataSource.METRICS : DataSource.TRACES;
const queryNameAndExpression = QUERYNAME_AND_EXPRESSION;
const timeAggregateOperators = [
MetricAggregateOperator.EMPTY,
MetricAggregateOperator.EMPTY,
MetricAggregateOperator.EMPTY,
];
const spaceAggregateOperators = [
MetricAggregateOperator.P50,
MetricAggregateOperator.P90,
MetricAggregateOperator.P99,
];
return getQueryBuilderQueries({
autocompleteData,
legends,
@@ -90,6 +101,8 @@ export const latency = ({
aggregateOperator,
dataSource,
queryNameAndExpression,
timeAggregateOperators,
spaceAggregateOperators,
});
};
@@ -510,11 +523,16 @@ export const operationPerSec = ({
const legends = OPERATION_LEGENDS;
const dataSource = DataSource.METRICS;
const timeAggregateOperators = [MetricAggregateOperator.RATE];
const spaceAggregateOperators = [MetricAggregateOperator.SUM];
return getQueryBuilderQueries({
autocompleteData,
legends,
filterItems,
dataSource,
timeAggregateOperators,
spaceAggregateOperators,
});
};

View File

@@ -29,6 +29,8 @@ export interface BuilderQueriesProps {
aggregateOperator?: string[];
dataSource: DataSource;
queryNameAndExpression?: string[];
timeAggregateOperators: MetricAggregateOperator[];
spaceAggregateOperators: MetricAggregateOperator[];
}
export interface BuilderQuerieswithFormulaProps {

View File

@@ -2,18 +2,27 @@
import { DownloadOptions } from 'container/Download/Download.types';
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
import {
MetricAggregateOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
export const legend = {
address: '{{address}}',
};
export const QUERYNAME_AND_EXPRESSION = ['A', 'B', 'C'];
export const LATENCY_AGGREGATEOPERATOR = ['p50', 'p90', 'p99'];
export const LATENCY_AGGREGATEOPERATOR_SPAN_METRICS = [
'hist_quantile_50',
'hist_quantile_90',
'hist_quantile_99',
export const LATENCY_AGGREGATEOPERATOR = [
TracesAggregatorOperator.P50,
TracesAggregatorOperator.P90,
TracesAggregatorOperator.P99,
];
export const LATENCY_AGGREGATEOPERATOR_SPAN_METRICS = [
MetricAggregateOperator.P50,
MetricAggregateOperator.P90,
MetricAggregateOperator.P99,
];
export const OPERATION_LEGENDS = ['Operations'];
export const MENU_ITEMS = [MenuItemKeys.View, MenuItemKeys.CreateAlerts];
@@ -21,8 +30,21 @@ export const MENU_ITEMS = [MenuItemKeys.View, MenuItemKeys.CreateAlerts];
export enum FORMULA {
ERROR_PERCENTAGE = 'A*100/B',
DATABASE_CALLS_AVG_DURATION = 'A/B',
// The apdex formula is (satisfied_count + 0.5 * tolerating_count + 0 * frustating_count) / total_count
// The satisfied_count is B, tolerating_count is C, total_count is A
// But why do we have (B+C)/2 instead of B + C/2?
// The way we issue the query is latency <= threshold, which means we over count i.e
// query B => durationNano <= 500ms
// query C => durationNano <= 2000ms
// Since <= 2000ms includes <= 500ms, we over count, to correct we subtract B/2
// so the full expression would be (B + C/2) - B/2 = (B+C)/2
APDEX_TRACES = '((B + C)/2)/A',
APDEX_DELTA_SPAN_METRICS = '((B + C)/2)/A',
// Does the same not apply for delta span metrics?
// No, because the delta metrics store the counts just for the current bucket
// so we don't need to subtract anything
APDEX_DELTA_SPAN_METRICS = '(B + C)/A',
// Cumulative span metrics store the counts for all buckets
// so we need to subtract B/2 to correct the over counting
APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A',
}
@@ -53,6 +75,7 @@ export enum KeyOperationTableHeader {
export enum MetricsType {
Tag = 'tag',
Resource = 'resource',
Scope = 'scope',
}
export enum WidgetKeys {

View File

@@ -0,0 +1,81 @@
.timezone-adaption {
padding: 16px;
background: var(--bg-ink-400);
border: 1px solid var(--bg-ink-500);
border-radius: 4px;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
&__title {
color: var(--bg-vanilla-300);
font-size: 14px;
font-weight: 500;
margin: 0;
}
&__description {
color: var(--bg-vanilla-400);
font-size: 14px;
line-height: 20px;
margin: 0 0 12px 0;
}
&__note {
display: flex;
align-items: center;
justify-content: space-between;
padding: 7.5px 12px;
background: rgba(78, 116, 248, 0.1);
border: 1px solid rgba(78, 116, 248, 0.1);
border-radius: 4px;
}
&__bullet {
color: var(--bg-robin-400);
font-size: 16px;
line-height: 20px;
}
&__note-text-container {
display: flex;
align-items: center;
gap: 10px;
}
&__note-text {
display: flex;
align-items: center;
gap: 4px;
color: var(--bg-robin-400);
font-size: 14px;
line-height: 20px;
}
&__note-text-overridden {
display: flex;
align-items: center;
padding: 0 2px;
background: rgba(171, 189, 255, 0.04);
border-radius: 2px;
font-size: 12px;
line-height: 16px;
color: var(--bg-vanilla-100);
}
&__clear-override {
display: flex;
align-items: center;
gap: 6px;
background: transparent;
border: none;
padding: 0;
color: var(--bg-robin-300);
font-size: 12px;
line-height: 16px; /* 133.333% */
letter-spacing: 0.12px;
cursor: pointer;
}
}

View File

@@ -0,0 +1,78 @@
import './TimezoneAdaptation.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Switch } from 'antd';
import { Delete } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useState } from 'react';
function TimezoneAdaptation(): JSX.Element {
const { timezone, browserTimezone, updateTimezone } = useTimezone();
const isTimezoneOverridden = useMemo(
() => timezone?.offset !== browserTimezone.offset,
[timezone, browserTimezone],
);
const [isAdaptationEnabled, setIsAdaptationEnabled] = useState(true);
const getSwitchStyles = (): React.CSSProperties => ({
backgroundColor:
isAdaptationEnabled && isTimezoneOverridden ? Color.BG_AMBER_400 : undefined,
});
const handleOverrideClear = (): void => {
updateTimezone(browserTimezone);
};
return (
<div className="timezone-adaption">
<div className="timezone-adaption__header">
<h2 className="timezone-adaption__title">Adapt to my timezone</h2>
<Switch
checked={isAdaptationEnabled}
onChange={setIsAdaptationEnabled}
style={getSwitchStyles()}
/>
</div>
<p className="timezone-adaption__description">
Adapt the timestamps shown in the SigNoz console to my active timezone.
</p>
<div className="timezone-adaption__note">
<div className="timezone-adaption__note-text-container">
<span className="timezone-adaption__bullet"></span>
<span className="timezone-adaption__note-text">
{isTimezoneOverridden ? (
<>
Your current timezone is overridden to
<span className="timezone-adaption__note-text-overridden">
{timezone?.offset}
</span>
</>
) : (
<>
You can override the timezone adaption for any view with the time
picker.
</>
)}
</span>
</div>
{!!isTimezoneOverridden && (
<button
type="button"
className="timezone-adaption__clear-override"
onClick={handleOverrideClear}
>
<Delete height={12} width={12} color={Color.BG_ROBIN_300} />
Clear override
</button>
)}
</div>
</div>
);
}
export default TimezoneAdaptation;

View File

@@ -7,6 +7,7 @@ import { LogOut, Moon, Sun } from 'lucide-react';
import { useState } from 'react';
import Password from './Password';
import TimezoneAdaptation from './TimezoneAdaptation/TimezoneAdaptation';
import UserInfo from './UserInfo';
function MySettings(): JSX.Element {
@@ -78,6 +79,8 @@ function MySettings(): JSX.Element {
<Password />
</div>
<TimezoneAdaptation />
<Button
className="flexBtn"
onClick={(): void => Logout()}

View File

@@ -0,0 +1,32 @@
&nbsp;
Once you are done instrumenting your Java application, you can run it using the below commands
**Note:**
- Ensure you have Java and Maven installed. Compile your Java consumer applications: Ensure your consumer apps are compiled and ready to run.
**Run Consumer App with Java Agent:**
```bash
java -javaagent:/path/to/opentelemetry-javaagent.jar \
-Dotel.service.name=consumer-svc \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=otlp \
-Dotel.instrumentation.kafka.producer-propagation.enabled=true \
-Dotel.instrumentation.kafka.experimental-span-attributes=true \
-Dotel.instrumentation.kafka.metric-reporter.enabled=true \
-jar /path/to/your/consumer.jar
```
<path> - update it to the path where you downloaded the Java JAR agent in previous step
<my-app> - Jar file of your application
&nbsp;
**Note:**
- In case you're dockerising your application, make sure to dockerise it along with OpenTelemetry instrumentation done in previous step.
&nbsp;
If you encounter any difficulties, please consult the [troubleshooting section](https://signoz.io/docs/instrumentation/springboot/#troubleshooting-your-installation) for assistance.

View File

@@ -0,0 +1,29 @@
&nbsp;
Once you are done instrumenting your Java application, you can run it using the below commands
**Note:**
- Ensure you have Java and Maven installed. Compile your Java producer applications: Ensure your producer apps are compiled and ready to run.
**Run Producer App with Java Agent:**
```bash
java -javaagent:/path/to/opentelemetry-javaagent.jar \
-Dotel.service.name=producer-svc \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=otlp \
-jar /path/to/your/producer.jar
```
<path> - update it to the path where you downloaded the Java JAR agent in previous step
<my-app> - Jar file of your application
&nbsp;
**Note:**
- In case you're dockerising your application, make sure to dockerise it along with OpenTelemetry instrumentation done in previous step.
&nbsp;
If you encounter any difficulties, please consult the [troubleshooting section](https://signoz.io/docs/instrumentation/springboot/#troubleshooting-your-installation) for assistance.

View File

@@ -312,7 +312,7 @@ export default function Onboarding(): JSX.Element {
<div
onClick={(): void => {
logEvent('Onboarding V2: Skip Button Clicked', {});
history.push('/');
history.push(ROUTES.APPLICATION);
}}
className="skip-to-console"
>

View File

@@ -6,11 +6,16 @@ import {
LoadingOutlined,
} from '@ant-design/icons';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import Header from 'container/OnboardingContainer/common/Header/Header';
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
import { useOnboardingStatus } from 'hooks/messagingQueue / onboarding/useOnboardingStatus';
import { useQueryService } from 'hooks/useQueryService';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import useUrlQuery from 'hooks/useUrlQuery';
import MessagingQueueHealthCheck from 'pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck';
import { getAttributeDataFromOnboardingStatus } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -27,6 +32,12 @@ export default function ConnectionStatus(): JSX.Element {
GlobalReducer
>((state) => state.globalTime);
const urlQuery = useUrlQuery();
const getStartedSource = urlQuery.get(QueryParams.getStartedSource);
const getStartedSourceService = urlQuery.get(
QueryParams.getStartedSourceService,
);
const {
serviceName,
selectedDataSource,
@@ -57,8 +68,69 @@ export default function ConnectionStatus(): JSX.Element {
maxTime,
selectedTime,
selectedTags,
options: {
enabled: getStartedSource !== 'kafka',
},
});
const [pollInterval, setPollInterval] = useState<number | false>(10000);
const {
data: onbData,
error: onbErr,
isFetching: onbFetching,
} = useOnboardingStatus(
{
enabled: getStartedSource === 'kafka',
refetchInterval: pollInterval,
},
getStartedSourceService || '',
'query-key-onboarding-status',
);
const [
shouldRetryOnboardingCall,
setShouldRetryOnboardingCall,
] = useState<boolean>(false);
useEffect(() => {
// runs only when the caller is coming from 'kafka' i.e. coming from Messaging Queues - setup helper
if (getStartedSource === 'kafka') {
if (onbData?.statusCode !== 200) {
setShouldRetryOnboardingCall(true);
} else if (onbData?.payload?.status === 'success') {
const attributeData = getAttributeDataFromOnboardingStatus(
onbData?.payload,
);
if (attributeData.overallStatus === 'success') {
setLoading(false);
setIsReceivingData(true);
setPollInterval(false);
} else {
setShouldRetryOnboardingCall(true);
}
}
}
}, [
shouldRetryOnboardingCall,
onbData,
onbErr,
onbFetching,
getStartedSource,
]);
useEffect(() => {
if (retryCount < 0 && getStartedSource === 'kafka') {
setPollInterval(false);
setLoading(false);
}
}, [retryCount, getStartedSource]);
useEffect(() => {
if (getStartedSource === 'kafka' && !onbFetching) {
setRetryCount((prevCount) => prevCount - 1);
}
}, [getStartedSource, onbData, onbFetching]);
const renderDocsReference = (): JSX.Element => {
switch (selectedDataSource?.name) {
case 'java':
@@ -192,25 +264,27 @@ export default function ConnectionStatus(): JSX.Element {
useEffect(() => {
let pollingTimer: string | number | NodeJS.Timer | undefined;
if (loading) {
pollingTimer = setInterval(() => {
// Trigger a refetch with the updated parameters
const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000;
const updatedMaxTime = Date.now() * 1000000;
if (getStartedSource !== 'kafka') {
if (loading) {
pollingTimer = setInterval(() => {
// Trigger a refetch with the updated parameters
const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000;
const updatedMaxTime = Date.now() * 1000000;
const payload = {
maxTime: updatedMaxTime,
minTime: updatedMinTime,
selectedTime,
};
const payload = {
maxTime: updatedMaxTime,
minTime: updatedMinTime,
selectedTime,
};
dispatch({
type: UPDATE_TIME_INTERVAL,
payload,
});
}, pollingInterval); // Same interval as pollingInterval
} else if (!loading && pollingTimer) {
clearInterval(pollingTimer);
dispatch({
type: UPDATE_TIME_INTERVAL,
payload,
});
}, pollingInterval); // Same interval as pollingInterval
} else if (!loading && pollingTimer) {
clearInterval(pollingTimer);
}
}
// Clean up the interval when the component unmounts
@@ -221,15 +295,24 @@ export default function ConnectionStatus(): JSX.Element {
}, [refetch, selectedTags, selectedTime, loading]);
useEffect(() => {
verifyApplicationData(data);
if (getStartedSource !== 'kafka') {
verifyApplicationData(data);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isServiceLoading, data, error, isError]);
useEffect(() => {
refetch();
if (getStartedSource !== 'kafka') {
refetch();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isQueryServiceLoading = useMemo(
() => isServiceLoading || loading || onbFetching,
[isServiceLoading, loading, onbFetching],
);
return (
<div className="connection-status-container">
<div className="full-docs-link">{renderDocsReference()}</div>
@@ -250,30 +333,42 @@ export default function ConnectionStatus(): JSX.Element {
<div className="label"> Status </div>
<div className="status">
{(loading || isServiceLoading) && <LoadingOutlined />}
{!(loading || isServiceLoading) && isReceivingData && (
<>
<CheckCircleTwoTone twoToneColor="#52c41a" />
<span> Success </span>
</>
)}
{!(loading || isServiceLoading) && !isReceivingData && (
<>
<CloseCircleTwoTone twoToneColor="#e84749" />
<span> Failed </span>
</>
)}
{isQueryServiceLoading && <LoadingOutlined />}
{!isQueryServiceLoading &&
isReceivingData &&
(getStartedSource !== 'kafka' ? (
<>
<CheckCircleTwoTone twoToneColor="#52c41a" />
<span> Success </span>
</>
) : (
<MessagingQueueHealthCheck
serviceToInclude={[getStartedSourceService || '']}
/>
))}
{!isQueryServiceLoading &&
!isReceivingData &&
(getStartedSource !== 'kafka' ? (
<>
<CloseCircleTwoTone twoToneColor="#e84749" />
<span> Failed </span>
</>
) : (
<MessagingQueueHealthCheck
serviceToInclude={[getStartedSourceService || '']}
/>
))}
</div>
</div>
<div className="details-info">
<div className="label"> Details </div>
<div className="details">
{(loading || isServiceLoading) && <div> Waiting for Update </div>}
{!(loading || isServiceLoading) && isReceivingData && (
{isQueryServiceLoading && <div> Waiting for Update </div>}
{!isQueryServiceLoading && isReceivingData && (
<div> Received data from the application successfully. </div>
)}
{!(loading || isServiceLoading) && !isReceivingData && (
{!isQueryServiceLoading && !isReceivingData && (
<div> Could not detect the install </div>
)}
</div>

View File

@@ -74,4 +74,11 @@ div[class*='-setup-instructions-container'] {
.dataSourceName {
color: var(--bg-slate-500);
}
}
}
.supported-languages-container {
.disabled {
cursor: not-allowed;
opacity: 0.5;
}
}

View File

@@ -6,15 +6,21 @@ import { LoadingOutlined } from '@ant-design/icons';
import { Button, Card, Form, Input, Select, Space, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
import { useCases } from 'container/OnboardingContainer/OnboardingContainer';
import {
ModulesMap,
useCases,
} from 'container/OnboardingContainer/OnboardingContainer';
import {
getDataSources,
getSupportedFrameworks,
hasFrameworks,
messagingQueueKakfaSupportedDataSources,
} from 'container/OnboardingContainer/utils/dataSourceUtils';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import { Blocks, Check } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -33,6 +39,8 @@ export default function DataSource(): JSX.Element {
const { t } = useTranslation(['common']);
const history = useHistory();
const getStartedSource = useUrlQuery().get(QueryParams.getStartedSource);
const {
serviceName,
selectedModule,
@@ -44,6 +52,9 @@ export default function DataSource(): JSX.Element {
updateSelectedFramework,
} = useOnboardingContext();
const isKafkaAPM =
getStartedSource === 'kafka' && selectedModule?.id === ModulesMap.APM;
const [supportedDataSources, setSupportedDataSources] = useState<
DataSourceType[]
>([]);
@@ -150,13 +161,19 @@ export default function DataSource(): JSX.Element {
className={cx(
'supported-language',
selectedDataSource?.name === dataSource.name ? 'selected' : '',
isKafkaAPM &&
!messagingQueueKakfaSupportedDataSources.includes(dataSource?.id || '')
? 'disabled'
: '',
)}
key={dataSource.name}
onClick={(): void => {
updateSelectedFramework(null);
updateSelectedEnvironment(null);
updateSelectedDataSource(dataSource);
form.setFieldsValue({ selectFramework: null });
if (!isKafkaAPM) {
updateSelectedFramework(null);
updateSelectedEnvironment(null);
updateSelectedDataSource(dataSource);
form.setFieldsValue({ selectFramework: null });
}
}}
>
<div>

View File

@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { QueryParams } from 'constants/query';
import { ApmDocFilePaths } from 'container/OnboardingContainer/constants/apmDocFilePaths';
import { AwsMonitoringDocFilePaths } from 'container/OnboardingContainer/constants/awsMonitoringDocFilePaths';
import { AzureMonitoringDocFilePaths } from 'container/OnboardingContainer/constants/azureMonitoringDocFilePaths';
@@ -10,6 +11,7 @@ import {
useOnboardingContext,
} from 'container/OnboardingContainer/context/OnboardingContext';
import { ModulesMap } from 'container/OnboardingContainer/OnboardingContainer';
import useUrlQuery from 'hooks/useUrlQuery';
import { useEffect, useState } from 'react';
export interface IngestionInfoProps {
@@ -31,6 +33,12 @@ export default function MarkdownStep(): JSX.Element {
const [markdownContent, setMarkdownContent] = useState('');
const urlQuery = useUrlQuery();
const getStartedSource = urlQuery.get(QueryParams.getStartedSource);
const getStartedSourceService = urlQuery.get(
QueryParams.getStartedSourceService,
);
const { step } = activeStep;
const getFilePath = (): any => {
@@ -54,6 +62,12 @@ export default function MarkdownStep(): JSX.Element {
path += `_${step?.id}`;
if (
getStartedSource === 'kafka' &&
path === 'APM_java_springBoot_kubernetes_recommendedSteps_runApplication' // todo: Sagar - Make this generic logic in followup PRs
) {
path += `_${getStartedSourceService}`;
}
return path;
};

Some files were not shown because too many files have changed in this diff Show More