Compare commits

..

31 Commits

Author SHA1 Message Date
Jatinderjit Singh
2379072a12 rename buildReceiverStage -> createReceiverStage 2026-05-04 19:00:51 +05:30
Jatinderjit Singh
4d18f7b91b refactor: remove dead orgID param from task constructors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:00:51 +05:30
Jatinderjit Singh
909347ae3d refactor: pass MaintenanceMuter directly to pipelineBuilder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:00:51 +05:30
Jatinderjit Singh
a39010ff47 chore: replace SPDX tag with full Apache 2.0 license boilerplate
The full license text is unambiguously compliant with Apache 2.0 Section 4(a),
which requires giving recipients "a copy of this License".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:00:51 +05:30
Jatinderjit Singh
5677ae8b0b chore: add license header to pipeline_builder.go
Copied code originates from Apache-2.0 licensed Prometheus Alertmanager;
add dual copyright + SPDX identifier following the repo's convention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:00:51 +05:30
Jatinderjit Singh
11ca05bdeb refactor: move maintenance mute stage into custom pipelineBuilder
Copy notify.PipelineBuilder locally so we can inject mms between the
silence stage and the receiver stage (GossipSettle → Inhibit →
TimeActive → TimeMute → Silence → mms → Receiver), matching the
correct suppression order the team requires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:00:51 +05:30
Jatinderjit Singh
ffac3ff8a4 refactor: wrap routing pipeline once instead of per-route injection
Replace the per-route-entry loop with a single MultiStage wrap so
maintenance suppression runs once per dispatch group before routing.
2026-05-04 19:00:51 +05:30
Jatinderjit Singh
bb8cfa1bb4 add maintenanceMuteStage to move planned maintenance to alertmanager
Rules previously skipped rule.Eval() entirely during maintenance windows.
This change moves suppression to MaintenanceMuter, injected as a Stage
in the alertmanager notification pipeline. Now rules always evaluate and
everys suppression is handled by alertmanager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:00:51 +05:30
Jatinderjit Singh
d7c22adf7d handle empty initial start time 2026-05-03 23:12:10 +05:30
Jatinderjit Singh
647c89e6cf remove redundant param shouldKeepLocalTime 2026-05-03 21:55:38 +05:30
Jatinderjit Singh
3a5322b42b fix display timezone 2026-05-03 21:27:46 +05:30
Jatinderjit Singh
ee56d0c061 Remove start and end time from recurrence 2026-05-03 17:34:11 +05:30
Jatinderjit Singh
7ad4446c42 Revert "send empty start/end dates in frontend for recurring windows"
This reverts commit 87bc3fae274ccfd9ce98aeae5ac379fadf657df3.
2026-05-03 17:34:11 +05:30
Jatinderjit Singh
52940da9ec handle zero start and end times in schedule 2026-05-03 17:34:11 +05:30
Jatinderjit Singh
01744e9e39 send empty start/end dates in frontend for recurring windows 2026-05-03 17:34:11 +05:30
Jatinderjit Singh
aad83debc8 fix: maintenance ignores recurrence when fixed times also set 2026-05-03 17:34:11 +05:30
Vinicius Lourenço
c6683e075e fix(tsgo): does not accept lint staged args (#11160)
Some checks failed
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / staging (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
2026-05-01 14:07:13 +00:00
Vinicius Lourenço
3bc936282e feat(tsgo): use tsgo to type-check (#11143) 2026-05-01 12:07:03 +00:00
Vinicius Lourenço
c3f44b31fe chore(unused-files): remove all unused files (#11150)
* chore(unused-files): remove all unused files

* test(logs): removed mocks of old/unused files
2026-05-01 11:36:46 +00:00
primus-bot[bot]
0c9f237369 chore(release): bump to v0.121.1 (#11154)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-04-30 16:17:50 +00:00
Vinicius Lourenço
8b13f004ed Revert "feat(global-time-store): add support to context, url persistence, store persistence, drift handle (#11081)" (#11152)
This reverts commit cc3da72aa5.
2026-04-30 15:46:28 +00:00
Abhi kumar
8c1d13bb38 fix: added fix for groupby being undefined (#11151) 2026-04-30 15:46:05 +00:00
SagarRajput-7
ad8f3328e0 fix(mcp-page): added acitve host url instead of current url on mcp page (#11141)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* fix(mcp-page): added acitve host url instead of current url on mcp page

* fix(mcp-page): configure access and role control

* chore: move get hosts api access to viewers (#11145)

* chore: move get hosts api access to viewers

* chore: update openapi spec

---------

Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>

* fix: allowed hosts api to run on all the cloud users

* fix: updated test cases

---------

Co-authored-by: Karan Balani <29383381+balanikaran@users.noreply.github.com>
2026-04-30 13:31:43 +00:00
Vinicius Lourenço
cc3da72aa5 feat(global-time-store): add support to context, url persistence, store persistence, drift handle (#11081)
* feat(global-time-store): add support to context, url persistence, store persistence, drift handle

* chore(fmt): fix issue with format

* refactor(hooks): mark internal and public ones

* refactor(store): adapt to don't need round down

* refactor(global-time): scope queries via name for auto refresh to be isolated

* chore(use-query-cache): add little doc

* chore(global-time): update docs
2026-04-30 11:11:58 +00:00
Nityananda Gohain
755390c4b5 feat: types and handler for llm pricing rules (#10908)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat: 1.Types for ai-o11y ricing rules

* fix: changes

* fix: minor changes

* fix: more changes

* fix: new updates

* fix: address comments

* fix: remove nullable

* fix: types

* fix: address comments

* fix: use mustnewuuid

* fix: correct table name

* fix: address comments and move pricing to a single struct

* fix: linting issues
2026-04-30 05:44:12 +00:00
SagarRajput-7
adbd89aae9 fix(platform): fix semantic tokens and component upgrade issue in platform surfaces (#11142)
* fix(platform): fix semantic tokens and component upgrade issue in platform surfaces

* fix: updated signozhq/ui version
2026-04-30 00:31:33 +00:00
primus-bot[bot]
b71de5b561 chore(release): bump to v0.121.0 (#11139)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-04-29 16:08:15 +00:00
Piyush Singariya
a672335a33 fix: Body Search warning with FTS in JSON Logs (#10807)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* fix: fts warning miss in direct text search

* fix: comments

* test: added one more test variation

* ci: go lint

* fix: fts warning update

* fix: integration tests

* fix: go test and fmtlint
2026-04-29 08:50:28 +00:00
Abhi kumar
f4e5534e53 chore: updated drilldown popup ui to match tooltip (#11113) 2026-04-29 06:55:01 +00:00
swapnil-signoz
14a032119a chore: bumping cloud integration agent version to v0.0.10 (#11135)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* chore: bumping agent version to v0.0.10

* chore: depployment
2026-04-29 05:23:22 +00:00
Yunus M
e78dfc1622 Azure service integration UI (#11117)
* feat: azure integration - ui refactor

* feat: implement AWS cloud account integration UI components and connection handling

* feat: add Azure cloud account integration UI components and connection handling

* feat: enhance Azure cloud account setup UI with prerequisites and accordion for how it works section

* feat: enhance styling for Azure and AWS account management

* refactor: clean up state initialization and destructuring in AWS and HeroSection components

* fix: update import path for ServiceDashboards in S3Sync test

* feat: add Denmark East region to Azure regions and enhance Azure account removal messaging

* chore: remove prefer-signoz-ui-icons ESLint rule and update telemetry event naming
2026-04-29 03:58:38 +00:00
366 changed files with 4540 additions and 11557 deletions

View File

@@ -190,7 +190,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.120.0
image: signoz/signoz:v0.121.1
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port

View File

@@ -117,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.120.0
image: signoz/signoz:v0.121.1
ports:
- "8080:8080" # signoz port
volumes:

View File

@@ -181,7 +181,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.120.0}
image: signoz/signoz:${VERSION:-v0.121.1}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -109,7 +109,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.120.0}
image: signoz/signoz:${VERSION:-v0.121.1}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -2103,8 +2103,6 @@ components:
type: boolean
org_id:
type: string
source:
type: string
updatedAt:
format: date-time
type: string
@@ -2136,7 +2134,6 @@ components:
type: object
DashboardtypesStorableDashboardData:
additionalProperties: {}
nullable: true
type: object
DashboardtypesUpdatablePublicDashboard:
properties:
@@ -2635,6 +2632,158 @@ components:
- list
- grouped_list
type: string
LlmpricingruletypesGettablePricingRules:
properties:
items:
items:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRule'
nullable: true
type: array
limit:
type: integer
offset:
type: integer
total:
type: integer
required:
- items
- total
- offset
- limit
type: object
LlmpricingruletypesLLMPricingCacheCosts:
properties:
mode:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleCacheMode'
read:
format: double
type: number
write:
format: double
type: number
required:
- mode
type: object
LlmpricingruletypesLLMPricingRule:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
enabled:
type: boolean
id:
type: string
isOverride:
type: boolean
modelName:
type: string
modelPattern:
$ref: '#/components/schemas/LlmpricingruletypesStringSlice'
orgId:
type: string
pricing:
$ref: '#/components/schemas/LlmpricingruletypesLLMRulePricing'
provider:
type: string
sourceId:
type: string
syncedAt:
format: date-time
nullable: true
type: string
unit:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleUnit'
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- orgId
- modelName
- provider
- modelPattern
- unit
- pricing
- isOverride
- enabled
type: object
LlmpricingruletypesLLMPricingRuleCacheMode:
enum:
- subtract
- additive
- unknown
type: string
LlmpricingruletypesLLMPricingRuleUnit:
enum:
- per_million_tokens
type: string
LlmpricingruletypesLLMRulePricing:
properties:
cache:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingCacheCosts'
input:
format: double
type: number
output:
format: double
type: number
required:
- input
- output
type: object
LlmpricingruletypesStringSlice:
items:
type: string
nullable: true
type: array
LlmpricingruletypesUpdatableLLMPricingRule:
properties:
enabled:
type: boolean
id:
nullable: true
type: string
isOverride:
nullable: true
type: boolean
modelName:
type: string
modelPattern:
items:
type: string
nullable: true
type: array
pricing:
$ref: '#/components/schemas/LlmpricingruletypesLLMRulePricing'
provider:
type: string
sourceId:
nullable: true
type: string
unit:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRuleUnit'
required:
- modelName
- provider
- modelPattern
- unit
- pricing
- enabled
type: object
LlmpricingruletypesUpdatableLLMPricingRules:
properties:
rules:
items:
$ref: '#/components/schemas/LlmpricingruletypesUpdatableLLMPricingRule'
nullable: true
type: array
required:
- rules
type: object
MetricsexplorertypesInspectMetricsRequest:
properties:
end:
@@ -4257,10 +4406,6 @@ components:
properties:
duration:
type: string
endTime:
format: date-time
nullable: true
type: string
repeatOn:
items:
$ref: '#/components/schemas/RuletypesRepeatOn'
@@ -4268,11 +4413,7 @@ components:
type: array
repeatType:
$ref: '#/components/schemas/RuletypesRepeatType'
startTime:
format: date-time
type: string
required:
- startTime
- duration
- repeatType
type: object
@@ -4439,6 +4580,7 @@ components:
type: string
required:
- timezone
- startTime
type: object
RuletypesScheduleType:
enum:
@@ -7678,6 +7820,218 @@ paths:
summary: Create bulk invite
tags:
- users
/api/v1/llm_pricing_rules:
get:
deprecated: false
description: Returns all LLM pricing rules for the authenticated org, with pagination.
operationId: ListLLMPricingRules
parameters:
- in: query
name: offset
schema:
type: integer
- in: query
name: limit
schema:
type: integer
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/LlmpricingruletypesGettablePricingRules'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List pricing rules
tags:
- llmpricingrules
put:
deprecated: false
description: Single write endpoint used by both the user and the Zeus sync job.
Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true)
are fully preserved when the request does not provide isOverride; only synced_at
is stamped.
operationId: CreateOrUpdateLLMPricingRules
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/LlmpricingruletypesUpdatableLLMPricingRules'
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create or update pricing rules
tags:
- llmpricingrules
/api/v1/llm_pricing_rules/{id}:
delete:
deprecated: false
description: Hard-deletes a pricing rule. If auto-synced, it will be recreated
on the next sync cycle.
operationId: DeleteLLMPricingRule
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete a pricing rule
tags:
- llmpricingrules
get:
deprecated: false
description: Returns a single LLM pricing rule by ID.
operationId: GetLLMPricingRule
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/LlmpricingruletypesLLMPricingRule'
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Get a pricing rule
tags:
- llmpricingrules
/api/v1/logs/promote_paths:
get:
deprecated: false
@@ -10058,186 +10412,6 @@ paths:
summary: Update a span mapper
tags:
- spanmapper
/api/v1/system/{source}:
get:
deprecated: false
description: This endpoint returns the system dashboard for the callers org
keyed by source (e.g. ai-o11y-overview).
operationId: GetSystemDashboard
parameters:
- in: path
name: source
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/DashboardtypesDashboard'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Get system dashboard
tags:
- systemdashboard
put:
deprecated: false
description: This endpoint replaces the system dashboard for the callers org
with the provided payload.
operationId: UpdateSystemDashboard
parameters:
- in: path
name: source
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DashboardtypesStorableDashboardData'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/DashboardtypesDashboard'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- EDITOR
- tokenizer:
- EDITOR
summary: Update system dashboard
tags:
- systemdashboard
/api/v1/system/{source}/reset:
post:
deprecated: false
description: This resets edited/updated system dashboard to default system dashboard.
operationId: ResetSystemDashboard
parameters:
- in: path
name: source
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/DashboardtypesDashboard'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- EDITOR
- tokenizer:
- EDITOR
summary: Reset system dashboard to defaults
tags:
- systemdashboard
/api/v1/testChannel:
post:
deprecated: true
@@ -17092,9 +17266,9 @@ paths:
description: Internal Server Error
security:
- api_key:
- ADMIN
- VIEWER
- tokenizer:
- ADMIN
- VIEWER
summary: Get host info from Zeus.
tags:
- zeus

View File

@@ -13,7 +13,6 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
@@ -49,7 +48,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, tr)
// create ch rule task for evaluation
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc)
} else if opts.Rule.RuleType == ruletypes.RuleTypeProm {
@@ -73,7 +72,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, pr)
// create promql rule task for evaluation
task = newTask(baserules.TaskTypeProm, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeProm, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc)
} else if opts.Rule.RuleType == ruletypes.RuleTypeAnomaly {
// create anomaly rule
@@ -96,7 +95,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, ar)
// create anomaly rule task for evaluation
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc)
} else {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, ruletypes.RuleTypeProm, ruletypes.RuleTypeThreshold)
@@ -210,9 +209,9 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
}
// newTask returns an appropriate group for the rule type
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, maintenanceStore ruletypes.MaintenanceStore, orgID valuer.UUID) baserules.Task {
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc) baserules.Task {
if taskType == baserules.TaskTypeCh {
return baserules.NewRuleTask(name, "", frequency, rules, opts, notify, maintenanceStore, orgID)
return baserules.NewRuleTask(name, "", frequency, rules, opts, notify)
}
return baserules.NewPromRuleTask(name, "", frequency, rules, opts, notify, maintenanceStore, orgID)
return baserules.NewPromRuleTask(name, "", frequency, rules, opts, notify)
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"project": ["src/**/*.ts", "src/**/*.tsx"],
"ignore": ["src/api/generated/**/*.ts"]
"ignore": ["src/api/generated/**/*.ts", "src/typings/*.ts"]
}

View File

@@ -51,7 +51,7 @@
"@signozhq/design-tokens": "2.1.4",
"@signozhq/icons": "0.1.0",
"@signozhq/resizable": "0.0.2",
"@signozhq/ui": "0.0.10",
"@signozhq/ui": "0.0.12",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",
@@ -198,6 +198,7 @@
"@types/redux-mock-store": "1.0.4",
"@types/styled-components": "^5.1.4",
"@types/uuid": "^8.3.1",
"@typescript/native-preview": "7.0.0-dev.20260421.2",
"autoprefixer": "10.4.19",
"babel-plugin-styled-components": "^1.12.0",
"eslint-plugin-sonarjs": "4.0.2",
@@ -240,7 +241,7 @@
"*.(js|jsx|ts|tsx)": [
"oxlint --fix",
"oxfmt --write",
"sh scripts/typecheck-staged.sh"
"sh -c tsgo --noEmit"
],
"*.(scss|css)": [
"stylelint"
@@ -266,4 +267,4 @@
"tmp": "0.2.4",
"vite": "npm:rolldown-vite@7.3.1"
}
}
}

View File

@@ -1,25 +0,0 @@
files="";
# lint-staged will pass all files in $1 $2 $3 etc. iterate and concat.
for var in "$@"
do
files="$files \"$var\","
done
# create temporary tsconfig which includes only passed files
str="{
\"extends\": \"./tsconfig.json\",
\"include\": [ \"src/typings/**/*.ts\",\"src/**/*.d.ts\", \"./babel.config.js\", \"./jest.config.ts\", \"./.eslintrc.js\",\"./__mocks__\",\"./public\",\"./tests\",\"./commitlint.config.ts\",\"./webpack.config.js\",\"./webpack.config.prod.js\",\"./jest.setup.ts\",\"./**/*.d.ts\",$files]
}"
echo $str > tsconfig.tmp
# run typecheck using temp config
tsc -p ./tsconfig.tmp
# capture exit code of tsc
code=$?
# delete temp config
rm ./tsconfig.tmp
exit $code

View File

@@ -0,0 +1,399 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
DeleteLLMPricingRulePathParameters,
GetLLMPricingRule200,
GetLLMPricingRulePathParameters,
ListLLMPricingRules200,
ListLLMPricingRulesParams,
LlmpricingruletypesUpdatableLLMPricingRulesDTO,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns all LLM pricing rules for the authenticated org, with pagination.
* @summary List pricing rules
*/
export const listLLMPricingRules = (
params?: ListLLMPricingRulesParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListLLMPricingRules200>({
url: `/api/v1/llm_pricing_rules`,
method: 'GET',
params,
signal,
});
};
export const getListLLMPricingRulesQueryKey = (
params?: ListLLMPricingRulesParams,
) => {
return [`/api/v1/llm_pricing_rules`, ...(params ? [params] : [])] as const;
};
export const getListLLMPricingRulesQueryOptions = <
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListLLMPricingRulesQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listLLMPricingRules>>
> = ({ signal }) => listLLMPricingRules(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListLLMPricingRulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listLLMPricingRules>>
>;
export type ListLLMPricingRulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List pricing rules
*/
export function useListLLMPricingRules<
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListLLMPricingRulesQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List pricing rules
*/
export const invalidateListLLMPricingRules = async (
queryClient: QueryClient,
params?: ListLLMPricingRulesParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListLLMPricingRulesQueryKey(params) },
options,
);
return queryClient;
};
/**
* Single write endpoint used by both the user and the Zeus sync job. Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true) are fully preserved when the request does not provide isOverride; only synced_at is stamped.
* @summary Create or update pricing rules
*/
export const createOrUpdateLLMPricingRules = (
llmpricingruletypesUpdatableLLMPricingRulesDTO: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: llmpricingruletypesUpdatableLLMPricingRulesDTO,
});
};
export const getCreateOrUpdateLLMPricingRulesMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationKey = ['createOrUpdateLLMPricingRules'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> }
> = (props) => {
const { data } = props ?? {};
return createOrUpdateLLMPricingRules(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateOrUpdateLLMPricingRulesMutationResult = NonNullable<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>
>;
export type CreateOrUpdateLLMPricingRulesMutationBody =
BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>;
export type CreateOrUpdateLLMPricingRulesMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create or update pricing rules
*/
export const useCreateOrUpdateLLMPricingRules = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createOrUpdateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationOptions =
getCreateOrUpdateLLMPricingRulesMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a pricing rule. If auto-synced, it will be recreated on the next sync cycle.
* @summary Delete a pricing rule
*/
export const deleteLLMPricingRule = ({
id,
}: DeleteLLMPricingRulePathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'DELETE',
});
};
export const getDeleteLLMPricingRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationKey = ['deleteLLMPricingRule'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
{ pathParams: DeleteLLMPricingRulePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteLLMPricingRule(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteLLMPricingRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteLLMPricingRule>>
>;
export type DeleteLLMPricingRuleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a pricing rule
*/
export const useDeleteLLMPricingRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationOptions = getDeleteLLMPricingRuleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns a single LLM pricing rule by ID.
* @summary Get a pricing rule
*/
export const getLLMPricingRule = (
{ id }: GetLLMPricingRulePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetLLMPricingRule200>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'GET',
signal,
});
};
export const getGetLLMPricingRuleQueryKey = ({
id,
}: GetLLMPricingRulePathParameters) => {
return [`/api/v1/llm_pricing_rules/${id}`] as const;
};
export const getGetLLMPricingRuleQueryOptions = <
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetLLMPricingRuleQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getLLMPricingRule>>
> = ({ signal }) => getLLMPricingRule({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetLLMPricingRuleQueryResult = NonNullable<
Awaited<ReturnType<typeof getLLMPricingRule>>
>;
export type GetLLMPricingRuleQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get a pricing rule
*/
export function useGetLLMPricingRule<
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetLLMPricingRuleQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get a pricing rule
*/
export const invalidateGetLLMPricingRule = async (
queryClient: QueryClient,
{ id }: GetLLMPricingRulePathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetLLMPricingRuleQueryKey({ id }) },
options,
);
return queryClient;
};

View File

@@ -2781,10 +2781,6 @@ export interface DashboardtypesDashboardDTO {
* @type string
*/
org_id?: string;
/**
* @type string
*/
source?: string;
/**
* @type string
* @format date-time
@@ -2827,12 +2823,9 @@ export interface DashboardtypesPostablePublicDashboardDTO {
timeRangeEnabled?: boolean;
}
/**
* @nullable
*/
export type DashboardtypesStorableDashboardDataDTO = {
export interface DashboardtypesStorableDashboardDataDTO {
[key: string]: unknown;
} | null;
}
export interface DashboardtypesUpdatablePublicDashboardDTO {
/**
@@ -3420,6 +3413,170 @@ export enum InframonitoringtypesResponseTypeDTO {
list = 'list',
grouped_list = 'grouped_list',
}
export interface LlmpricingruletypesGettablePricingRulesDTO {
/**
* @type array
* @nullable true
*/
items: LlmpricingruletypesLLMPricingRuleDTO[] | null;
/**
* @type integer
*/
limit: number;
/**
* @type integer
*/
offset: number;
/**
* @type integer
*/
total: number;
}
export interface LlmpricingruletypesLLMPricingCacheCostsDTO {
mode: LlmpricingruletypesLLMPricingRuleCacheModeDTO;
/**
* @type number
* @format double
*/
read?: number;
/**
* @type number
* @format double
*/
write?: number;
}
export interface LlmpricingruletypesLLMPricingRuleDTO {
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
id: string;
/**
* @type boolean
*/
isOverride: boolean;
/**
* @type string
*/
modelName: string;
modelPattern: LlmpricingruletypesStringSliceDTO;
/**
* @type string
*/
orgId: string;
pricing: LlmpricingruletypesLLMRulePricingDTO;
/**
* @type string
*/
provider: string;
/**
* @type string
*/
sourceId?: string;
/**
* @type string
* @format date-time
* @nullable true
*/
syncedAt?: Date | null;
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export enum LlmpricingruletypesLLMPricingRuleCacheModeDTO {
subtract = 'subtract',
additive = 'additive',
unknown = 'unknown',
}
export enum LlmpricingruletypesLLMPricingRuleUnitDTO {
per_million_tokens = 'per_million_tokens',
}
export interface LlmpricingruletypesLLMRulePricingDTO {
cache?: LlmpricingruletypesLLMPricingCacheCostsDTO;
/**
* @type number
* @format double
*/
input: number;
/**
* @type number
* @format double
*/
output: number;
}
/**
* @nullable
*/
export type LlmpricingruletypesStringSliceDTO = string[] | null;
export interface LlmpricingruletypesUpdatableLLMPricingRuleDTO {
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
* @nullable true
*/
id?: string | null;
/**
* @type boolean
* @nullable true
*/
isOverride?: boolean | null;
/**
* @type string
*/
modelName: string;
/**
* @type array
* @nullable true
*/
modelPattern: string[] | null;
pricing: LlmpricingruletypesLLMRulePricingDTO;
/**
* @type string
*/
provider: string;
/**
* @type string
* @nullable true
*/
sourceId?: string | null;
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
}
export interface LlmpricingruletypesUpdatableLLMPricingRulesDTO {
/**
* @type array
* @nullable true
*/
rules: LlmpricingruletypesUpdatableLLMPricingRuleDTO[] | null;
}
export interface MetricsexplorertypesInspectMetricsRequestDTO {
/**
* @type integer
@@ -5197,23 +5354,12 @@ export interface RuletypesRecurrenceDTO {
* @type string
*/
duration: string;
/**
* @type string
* @format date-time
* @nullable true
*/
endTime?: Date | null;
/**
* @type array
* @nullable true
*/
repeatOn?: RuletypesRepeatOnDTO[] | null;
repeatType: RuletypesRepeatTypeDTO;
/**
* @type string
* @format date-time
*/
startTime: Date;
}
export interface RuletypesRenotifyDTO {
@@ -5398,7 +5544,7 @@ export interface RuletypesScheduleDTO {
* @type string
* @format date-time
*/
startTime?: Date;
startTime: Date;
/**
* @type string
*/
@@ -7011,6 +7157,41 @@ export type CreateInvite201 = {
status: string;
};
export type ListLLMPricingRulesParams = {
/**
* @type integer
* @description undefined
*/
offset?: number;
/**
* @type integer
* @description undefined
*/
limit?: number;
};
export type ListLLMPricingRules200 = {
data: LlmpricingruletypesGettablePricingRulesDTO;
/**
* @type string
*/
status: string;
};
export type DeleteLLMPricingRulePathParameters = {
id: string;
};
export type GetLLMPricingRulePathParameters = {
id: string;
};
export type GetLLMPricingRule200 = {
data: LlmpricingruletypesLLMPricingRuleDTO;
/**
* @type string
*/
status: string;
};
export type ListPromotedAndIndexedPaths200 = {
/**
* @type array
@@ -7342,39 +7523,6 @@ export type UpdateSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type GetSystemDashboardPathParameters = {
source: string;
};
export type GetSystemDashboard200 = {
data: DashboardtypesDashboardDTO;
/**
* @type string
*/
status: string;
};
export type UpdateSystemDashboardPathParameters = {
source: string;
};
export type UpdateSystemDashboard200 = {
data: DashboardtypesDashboardDTO;
/**
* @type string
*/
status: string;
};
export type ResetSystemDashboardPathParameters = {
source: string;
};
export type ResetSystemDashboard200 = {
data: DashboardtypesDashboardDTO;
/**
* @type string
*/
status: string;
};
export type ListUsersDeprecated200 = {
/**
* @type array

View File

@@ -1,318 +0,0 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
DashboardtypesStorableDashboardDataDTO,
GetSystemDashboard200,
GetSystemDashboardPathParameters,
RenderErrorResponseDTO,
ResetSystemDashboard200,
ResetSystemDashboardPathParameters,
UpdateSystemDashboard200,
UpdateSystemDashboardPathParameters,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* This endpoint returns the system dashboard for the callers org keyed by source (e.g. ai-o11y-overview).
* @summary Get system dashboard
*/
export const getSystemDashboard = (
{ source }: GetSystemDashboardPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetSystemDashboard200>({
url: `/api/v1/system/${source}`,
method: 'GET',
signal,
});
};
export const getGetSystemDashboardQueryKey = ({
source,
}: GetSystemDashboardPathParameters) => {
return [`/api/v1/system/${source}`] as const;
};
export const getGetSystemDashboardQueryOptions = <
TData = Awaited<ReturnType<typeof getSystemDashboard>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ source }: GetSystemDashboardPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getSystemDashboard>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetSystemDashboardQueryKey({ source });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getSystemDashboard>>
> = ({ signal }) => getSystemDashboard({ source }, signal);
return {
queryKey,
queryFn,
enabled: !!source,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getSystemDashboard>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetSystemDashboardQueryResult = NonNullable<
Awaited<ReturnType<typeof getSystemDashboard>>
>;
export type GetSystemDashboardQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get system dashboard
*/
export function useGetSystemDashboard<
TData = Awaited<ReturnType<typeof getSystemDashboard>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ source }: GetSystemDashboardPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getSystemDashboard>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetSystemDashboardQueryOptions({ source }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get system dashboard
*/
export const invalidateGetSystemDashboard = async (
queryClient: QueryClient,
{ source }: GetSystemDashboardPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetSystemDashboardQueryKey({ source }) },
options,
);
return queryClient;
};
/**
* This endpoint replaces the system dashboard for the callers org with the provided payload.
* @summary Update system dashboard
*/
export const updateSystemDashboard = (
{ source }: UpdateSystemDashboardPathParameters,
dashboardtypesStorableDashboardDataDTO: BodyType<DashboardtypesStorableDashboardDataDTO>,
) => {
return GeneratedAPIInstance<UpdateSystemDashboard200>({
url: `/api/v1/system/${source}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: dashboardtypesStorableDashboardDataDTO,
});
};
export const getUpdateSystemDashboardMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSystemDashboard>>,
TError,
{
pathParams: UpdateSystemDashboardPathParameters;
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSystemDashboard>>,
TError,
{
pathParams: UpdateSystemDashboardPathParameters;
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
},
TContext
> => {
const mutationKey = ['updateSystemDashboard'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateSystemDashboard>>,
{
pathParams: UpdateSystemDashboardPathParameters;
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSystemDashboard(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSystemDashboardMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSystemDashboard>>
>;
export type UpdateSystemDashboardMutationBody =
BodyType<DashboardtypesStorableDashboardDataDTO>;
export type UpdateSystemDashboardMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update system dashboard
*/
export const useUpdateSystemDashboard = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSystemDashboard>>,
TError,
{
pathParams: UpdateSystemDashboardPathParameters;
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSystemDashboard>>,
TError,
{
pathParams: UpdateSystemDashboardPathParameters;
data: BodyType<DashboardtypesStorableDashboardDataDTO>;
},
TContext
> => {
const mutationOptions = getUpdateSystemDashboardMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This resets edited/updated system dashboard to default system dashboard.
* @summary Reset system dashboard to defaults
*/
export const resetSystemDashboard = (
{ source }: ResetSystemDashboardPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ResetSystemDashboard200>({
url: `/api/v1/system/${source}/reset`,
method: 'POST',
signal,
});
};
export const getResetSystemDashboardMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof resetSystemDashboard>>,
TError,
{ pathParams: ResetSystemDashboardPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof resetSystemDashboard>>,
TError,
{ pathParams: ResetSystemDashboardPathParameters },
TContext
> => {
const mutationKey = ['resetSystemDashboard'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof resetSystemDashboard>>,
{ pathParams: ResetSystemDashboardPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return resetSystemDashboard(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type ResetSystemDashboardMutationResult = NonNullable<
Awaited<ReturnType<typeof resetSystemDashboard>>
>;
export type ResetSystemDashboardMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Reset system dashboard to defaults
*/
export const useResetSystemDashboard = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof resetSystemDashboard>>,
TError,
{ pathParams: ResetSystemDashboardPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof resetSystemDashboard>>,
TError,
{ pathParams: ResetSystemDashboardPathParameters },
TContext
> => {
const mutationOptions = getResetSystemDashboardMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -0,0 +1,22 @@
.codeBlock {
position: relative;
}
.codeBlockSyntaxHighlighter {
background-color: var(--l2-background) !important;
border-radius: 4px !important;
border: 1px solid var(--l2-border) !important;
color: var(--l2-foreground) !important;
pre {
color: var(--l2-foreground) !important;
font-family: 'Geist Mono' !important;
font-size: 12px !important;
}
code {
color: var(--l1-foreground) !important;
font-family: 'Geist Mono' !important;
font-size: 12px !important;
}
}

View File

@@ -0,0 +1,46 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import CodeBlock from './CodeBlock';
const mockCopyToClipboard = jest.fn();
jest.mock('react-use', () => ({
useCopyToClipboard: (): [unknown, (text: string) => void] => [
undefined,
mockCopyToClipboard,
],
}));
describe('CodeBlock', () => {
beforeEach(() => {
mockCopyToClipboard.mockReset();
});
it('renders code block mode by default', () => {
render(<CodeBlock code={'const x = 1;\n'} language="javascript" />);
const container = screen.getByTestId('code-block-container');
expect(container).toBeInTheDocument();
expect(container).toHaveTextContent('const x = 1;');
});
it('renders inline code when inline is true', () => {
render(<CodeBlock code="inline value" inline />);
const inlineCode = screen.getByText('inline value');
expect(inlineCode.tagName.toLowerCase()).toBe('code');
expect(screen.queryByTestId('code-block-container')).not.toBeInTheDocument();
});
it('copies code and triggers callback', async () => {
const onCopy = jest.fn();
render(<CodeBlock code="SELECT * FROM logs;" onCopy={onCopy} />);
fireEvent.click(screen.getByRole('button', { name: /copy code/i }));
await waitFor(() => {
expect(mockCopyToClipboard).toHaveBeenCalledWith('SELECT * FROM logs;');
});
expect(onCopy).toHaveBeenCalledWith('SELECT * FROM logs;');
});
});

View File

@@ -0,0 +1,89 @@
import { useMemo, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Check, Copy } from '@signozhq/icons';
import { Button } from '@signozhq/ui';
import SyntaxHighlighter, {
a11yDark,
} from 'components/MarkdownRenderer/syntaxHighlighter';
import styles from './CodeBlock.module.scss';
export interface CodeBlockProps {
code: string;
language?: string;
className?: string;
inline?: boolean;
showLineNumbers?: boolean;
showCopyButton?: boolean;
onCopy?: (copiedCode: string) => void;
}
function CodeBlock({
code,
language = 'text',
className,
inline = false,
showLineNumbers = false,
showCopyButton = true,
onCopy,
}: CodeBlockProps): JSX.Element {
const [isCopied, setIsCopied] = useState(false);
const [, copyToClipboard] = useCopyToClipboard();
const normalizedCode = useMemo(() => code?.replace(/\n$/, '') ?? '', [code]);
const handleCopy = (): void => {
copyToClipboard(normalizedCode);
setIsCopied(true);
onCopy?.(normalizedCode);
setTimeout(() => {
setIsCopied(false);
}, 1000);
};
if (inline) {
return <code className={className}>{normalizedCode}</code>;
}
return (
<div
className={`${styles.codeBlock} ${className}`}
style={{ position: 'relative' }}
data-testid="code-block-container"
>
{showCopyButton ? (
<Button
variant="ghost"
color="secondary"
size="sm"
onClick={handleCopy}
prefix={isCopied ? <Check size={14} /> : <Copy size={14} />}
aria-label="Copy code"
title={isCopied ? 'Copied' : 'Copy'}
style={{ position: 'absolute', right: 8, top: 8, zIndex: 1 }}
/>
) : null}
<SyntaxHighlighter
style={a11yDark}
language={language}
PreTag="div"
showLineNumbers={showLineNumbers}
wrapLongLines
className={styles.codeBlockSyntaxHighlighter}
>
{normalizedCode}
</SyntaxHighlighter>
</div>
);
}
CodeBlock.defaultProps = {
language: 'text',
className: undefined,
inline: false,
showLineNumbers: false,
showCopyButton: true,
onCopy: undefined,
};
export default CodeBlock;

View File

@@ -1,33 +0,0 @@
.error-state-container {
height: 240px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
border-radius: 3px;
.error-state-container-content {
display: flex;
flex-direction: column;
gap: 8px;
.error-state-text {
font-size: 14px;
font-weight: 500;
}
.error-state-additional-messages {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 4px;
.error-state-additional-text {
font-size: 12px;
font-weight: 400;
margin-left: 8px;
}
}
}
}

View File

@@ -1,59 +0,0 @@
import { Typography } from 'antd';
import APIError from '../../types/api/error';
import './Common.styles.scss';
interface ErrorStateComponentProps {
message?: string;
error?: APIError;
}
const defaultProps: Partial<ErrorStateComponentProps> = {
message: undefined,
error: undefined,
};
function ErrorStateComponent({
message,
error,
}: ErrorStateComponentProps): JSX.Element {
// Handle API Error object
if (error) {
const mainMessage = error.getErrorMessage();
const additionalErrors = error.getErrorDetails().error.errors || [];
return (
<div className="error-state-container">
<div className="error-state-container-content">
<Typography className="error-state-text">{mainMessage}</Typography>
{additionalErrors.length > 0 && (
<div className="error-state-additional-messages">
{additionalErrors.map((additionalError) => (
<Typography
key={`error-${additionalError.message}`}
className="error-state-additional-text"
>
{additionalError.message}
</Typography>
))}
</div>
)}
</div>
</div>
);
}
// Handle simple string message (backwards compatibility)
return (
<div className="error-state-container">
<div className="error-state-container-content">
<Typography className="error-state-text">{message}</Typography>
</div>
</div>
);
}
ErrorStateComponent.defaultProps = defaultProps;
export default ErrorStateComponent;

View File

@@ -1,4 +0,0 @@
.custom-date-picker {
display: flex;
flex-direction: column;
}

View File

@@ -1,105 +0,0 @@
import { Dispatch, SetStateAction, useMemo } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { DatePicker } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import {
CustomTimeType,
LexicalContext,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import dayjs, { Dayjs } from 'dayjs';
import { useTimezone } from 'providers/Timezone';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import './RangePickerModal.styles.scss';
interface RangePickerModalProps {
setCustomDTPickerVisible: Dispatch<SetStateAction<boolean>>;
setIsOpen: Dispatch<SetStateAction<boolean>>;
onCustomDateHandler: (
dateTimeRange: DateTimeRangeType,
lexicalContext?: LexicalContext | undefined,
) => void;
selectedTime: string;
onTimeChange?: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
}
function RangePickerModal(props: RangePickerModalProps): JSX.Element {
const {
setCustomDTPickerVisible,
setIsOpen,
onCustomDateHandler,
selectedTime,
onTimeChange,
} = props;
const { RangePicker } = DatePicker;
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
// 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).
const disabledDate = (current: any): boolean => {
const currentDay = dayjs(current);
return currentDay.isAfter(dayjs());
};
const onPopoverClose = (visible: boolean): void => {
if (!visible) {
setCustomDTPickerVisible(false);
}
setIsOpen(visible);
};
const onModalOkHandler = (date_time: any): void => {
if (date_time?.[1]) {
onPopoverClose(false);
}
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
};
const { timezone } = useTimezone();
const rangeValue: [Dayjs, Dayjs] = useMemo(
() => [
dayjs(minTime / 1000_000).tz(timezone.value),
dayjs(maxTime / 1000_000).tz(timezone.value),
],
[maxTime, minTime, timezone.value],
);
return (
<div className="custom-date-picker">
<RangePicker
disabledDate={disabledDate}
allowClear
showTime
format={(date: Dayjs): string =>
date.tz(timezone.value).format(DATE_TIME_FORMATS.ISO_DATETIME)
}
onOk={onModalOkHandler}
data-1p-ignore
{...(selectedTime === 'custom' &&
!onTimeChange && {
value: rangeValue,
})}
// use default value if onTimeChange is provided
{...(selectedTime === 'custom' &&
onTimeChange && {
defaultValue: rangeValue,
})}
/>
</div>
);
}
RangePickerModal.defaultProps = {
onTimeChange: undefined,
};
export default RangePickerModal;

View File

@@ -1,93 +0,0 @@
.details-drawer {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--l1-border);
}
.ant-drawer-header {
background: var(--l2-background);
border-bottom: 1px solid var(--l1-border);
.ant-drawer-header-title {
display: flex;
align-items: center;
.ant-drawer-close {
margin-inline-end: 0px;
padding: 0px;
padding-right: 16px;
border-right: 1px solid var(--l1-border);
}
.ant-drawer-title {
padding-left: 16px;
color: var(--l2-foreground);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
}
.ant-drawer-body {
padding: 16px;
background: var(--l2-background);
&::-webkit-scrollbar {
width: 0.1rem;
}
}
.details-drawer-tabs {
margin-top: 32px;
.ant-tabs-tab {
display: flex;
align-items: center;
justify-content: center;
width: 114px;
height: 32px;
flex-shrink: 0;
padding: 7px 20px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
color: var(--l1-foreground);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: -0.06px;
.ant-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 0px;
}
.ant-btn:hover {
background: unset;
}
}
.ant-tabs-tab-active {
background: var(--l3-background);
}
.ant-tabs-tab + .ant-tabs-tab {
margin-left: 0px;
}
.ant-tabs-nav::before {
border-bottom: 0px;
}
.ant-tabs-ink-bar {
background: none;
}
}
}

View File

@@ -1,57 +0,0 @@
import { Dispatch, SetStateAction } from 'react';
import { Drawer, Tabs, TabsProps } from 'antd';
import cx from 'classnames';
import './DetailsDrawer.styles.scss';
interface IDetailsDrawerProps {
open: boolean;
setOpen: Dispatch<SetStateAction<boolean>>;
title: string;
descriptiveContent: JSX.Element;
defaultActiveKey: string;
items: TabsProps['items'];
detailsDrawerClassName?: string;
tabBarExtraContent?: JSX.Element;
}
function DetailsDrawer(props: IDetailsDrawerProps): JSX.Element {
const {
open,
setOpen,
title,
descriptiveContent,
defaultActiveKey,
detailsDrawerClassName,
items,
tabBarExtraContent,
} = props;
return (
<Drawer
width="60%"
open={open}
afterOpenChange={setOpen}
mask={false}
title={title}
onClose={(): void => setOpen(false)}
className="details-drawer"
>
<div>{descriptiveContent}</div>
<Tabs
items={items}
addIcon
defaultActiveKey={defaultActiveKey}
animated
className={cx('details-drawer-tabs', detailsDrawerClassName)}
tabBarExtraContent={tabBarExtraContent}
/>
</Drawer>
);
}
DetailsDrawer.defaultProps = {
detailsDrawerClassName: '',
tabBarExtraContent: null,
};
export default DetailsDrawer;

View File

@@ -46,6 +46,7 @@ function DeleteMemberDialog({
color="destructive"
disabled={isDeleting}
onClick={onConfirm}
loading={isDeleting}
>
<Trash2 size={12} />
{isDeleting ? 'Processing...' : title}
@@ -63,7 +64,6 @@ function DeleteMemberDialog({
}}
title={title}
width="narrow"
className="alert-dialog delete-dialog"
showCloseButton={false}
disableOutsideClick={false}
footer={footer}

View File

@@ -28,18 +28,6 @@
cursor: default;
}
&__input {
height: 32px;
background: var(--l2-background);
border-color: var(--l1-border);
color: var(--l1-foreground);
box-shadow: none;
&::placeholder {
color: var(--l3-foreground);
}
}
&__input-wrapper {
display: flex;
align-items: center;
@@ -48,7 +36,7 @@
padding: var(--padding-1) var(--padding-2);
border-radius: 2px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border: 1px solid var(--border);
box-sizing: border-box;
&--disabled {
@@ -65,8 +53,8 @@
}
&__email-text {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
line-height: var(--line-height-18);
letter-spacing: -0.07px;
@@ -178,36 +166,6 @@
}
}
.delete-dialog {
background: var(--l2-background);
border: 1px solid var(--l1-border);
[data-slot='dialog-title'] {
color: var(--l1-foreground);
}
&__body {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--l2-foreground);
line-height: var(--paragraph-base-400-line-height);
letter-spacing: -0.065px;
margin: 0;
strong {
font-weight: var(--font-weight-medium);
color: var(--l1-foreground);
}
}
&__footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-4);
margin-top: var(--margin-6);
}
}
.reset-link-dialog {
background: var(--l2-background);
border: 1px solid var(--l1-border);
@@ -264,13 +222,6 @@
}
&__copy-btn {
flex-shrink: 0;
height: 32px;
border-radius: 0 2px 2px 0;
border-top: none;
border-right: none;
border-bottom: none;
border-left: 1px solid var(--l1-border);
min-width: 64px;
}
}

View File

@@ -224,7 +224,7 @@ function EditMemberDrawer({
try {
await rawRetry();
setSaveErrors((prev) => prev.filter((e) => e.context !== context));
refetchUser();
void refetchUser();
} catch (err) {
setSaveErrors((prev) =>
prev.map((e) =>
@@ -250,7 +250,7 @@ function EditMemberDrawer({
});
}
setSaveErrors((prev) => prev.filter((e) => e.context !== 'Name update'));
refetchUser();
void refetchUser();
} catch (err) {
setSaveErrors((prev) =>
prev.map((e) =>
@@ -319,7 +319,7 @@ function EditMemberDrawer({
}),
];
});
refetchUser();
void refetchUser();
},
});
} else {
@@ -340,7 +340,7 @@ function EditMemberDrawer({
onComplete();
}
refetchUser();
void refetchUser();
} finally {
setIsSaving(false);
}
@@ -465,7 +465,6 @@ function EditMemberDrawer({
prev.filter((err) => err.context !== 'Name update'),
);
}}
className="edit-member-drawer__input"
placeholder="Enter name"
disabled={isRootUser || isDeleted}
/>
@@ -631,7 +630,7 @@ function EditMemberDrawer({
</div>
<div className="edit-member-drawer__footer-right">
<Button variant="solid" color="secondary" onClick={handleClose}>
<Button variant="outlined" color="secondary" onClick={handleClose}>
<X size={14} />
Cancel
</Button>
@@ -641,6 +640,7 @@ function EditMemberDrawer({
color="primary"
disabled={!isDirty || isSaving || isRootUser}
onClick={handleSave}
loading={isSaving}
>
{isSaving ? 'Saving...' : 'Save Member Details'}
</Button>

View File

@@ -44,9 +44,8 @@ function ResetLinkDialog({
<span className="reset-link-dialog__link-text">{resetLink}</span>
</div>
<Button
variant="outlined"
variant="link"
color="secondary"
size="sm"
onClick={onCopy}
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
className="reset-link-dialog__copy-btn"

View File

@@ -1,143 +0,0 @@
import { useState } from 'react';
import { Button } from 'antd';
import { withErrorBoundary } from './index';
/**
* Example component that can throw errors
*/
function ProblematicComponent(): JSX.Element {
const [shouldThrow, setShouldThrow] = useState(false);
if (shouldThrow) {
throw new Error('This is a test error from ProblematicComponent!');
}
return (
<div style={{ padding: '20px' }}>
<h3>Problematic Component</h3>
<p>This component can throw errors when the button is clicked.</p>
<Button type="primary" onClick={(): void => setShouldThrow(true)} danger>
Trigger Error
</Button>
</div>
);
}
/**
* Basic usage - wraps component with default error boundary
*/
export const SafeProblematicComponent = withErrorBoundary(ProblematicComponent);
/**
* Usage with custom fallback component
*/
function CustomErrorFallback(): JSX.Element {
return (
<div
style={{ padding: '20px', border: '1px solid red', borderRadius: '4px' }}
>
<h4 style={{ color: 'red' }}>Custom Error Fallback</h4>
<p>Something went wrong in this specific component!</p>
<Button onClick={(): void => window.location.reload()}>Reload Page</Button>
</div>
);
}
export const SafeProblematicComponentWithCustomFallback = withErrorBoundary(
ProblematicComponent,
{
fallback: <CustomErrorFallback />,
},
);
/**
* Usage with custom error handler
*/
export const SafeProblematicComponentWithErrorHandler = withErrorBoundary(
ProblematicComponent,
{
onError: (error, errorInfo) => {
console.error('Custom error handler:', error);
console.error('Error info:', errorInfo);
// You could also send to analytics, logging service, etc.
},
sentryOptions: {
tags: {
section: 'dashboard',
priority: 'high',
},
level: 'error',
},
},
);
/**
* Example of wrapping an existing component from the codebase
*/
function ExistingComponent({
title,
data,
}: {
title: string;
data: any[];
}): JSX.Element {
// This could be any existing component that might throw errors
return (
<div>
<h4>{title}</h4>
<ul>
{data.map((item, index) => (
// eslint-disable-next-line react/no-array-index-key
<li key={index}>{item.name}</li>
))}
</ul>
</div>
);
}
export const SafeExistingComponent = withErrorBoundary(ExistingComponent, {
sentryOptions: {
tags: {
component: 'ExistingComponent',
feature: 'data-display',
},
},
});
/**
* Usage examples in a container component
*/
export function ErrorBoundaryExamples(): JSX.Element {
const sampleData = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' },
];
return (
<div style={{ padding: '20px' }}>
<h2>Error Boundary HOC Examples</h2>
<div style={{ marginBottom: '20px' }}>
<h3>1. Basic Usage</h3>
<SafeProblematicComponent />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>2. With Custom Fallback</h3>
<SafeProblematicComponentWithCustomFallback />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>3. With Custom Error Handler</h3>
<SafeProblematicComponentWithErrorHandler />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>4. Wrapped Existing Component</h3>
<SafeExistingComponent title="Sample Data" data={sampleData} />
</div>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Style } from '@signozhq/design-tokens';
import { ChevronDown, CircleAlert, Plus, Trash2, X } from '@signozhq/icons';
import { ChevronDown, Plus, Trash2, X } from '@signozhq/icons';
import {
Button,
Callout,
@@ -294,10 +294,8 @@ function InviteMembersModal({
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
>
{getValidationErrorMessage()}
</Callout>
title={getValidationErrorMessage()}
/>
</div>
)}
</div>

View File

@@ -1,13 +0,0 @@
.query-builder-search-wrapper {
margin-top: 10px;
border: 1px solid var(--l1-border);
border-bottom: none;
.ant-select-selector {
border: none !important;
input {
font-size: 12px;
}
}
}

View File

@@ -1,79 +0,0 @@
import { Dispatch, SetStateAction, useEffect } from 'react';
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import { ILog } from 'types/api/logs/log';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import './QueryBuilderSearchWrapper.styles.scss';
function QueryBuilderSearchWrapper({
log,
filters,
contextQuery,
isEdit,
suffixIcon,
setFilters,
setContextQuery,
}: QueryBuilderSearchWraperProps): JSX.Element {
const initialContextQuery = useInitialQuery(log);
useEffect(() => {
setContextQuery(initialContextQuery);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleSearch = (tagFilters: TagFilter): void => {
const tagFiltersLength = tagFilters.items.length;
if (
(!tagFiltersLength && (!filters || !filters.items.length)) ||
tagFiltersLength === filters?.items.length ||
!contextQuery
) {
return;
}
const nextQuery: Query = {
...contextQuery,
builder: {
...contextQuery.builder,
queryData: contextQuery.builder.queryData.map((item) => ({
...item,
filters: tagFilters,
})),
},
};
setFilters({ ...tagFilters });
setContextQuery({ ...nextQuery });
};
if (!contextQuery || !isEdit) {
return <></>;
}
return (
<QueryBuilderSearch
query={contextQuery?.builder.queryData[0]}
onChange={handleSearch}
className="query-builder-search-wrapper"
suffixIcon={suffixIcon}
/>
);
}
interface QueryBuilderSearchWraperProps {
log: ILog;
isEdit: boolean;
contextQuery: Query | undefined;
setContextQuery: Dispatch<SetStateAction<Query | undefined>>;
filters: TagFilter | null;
setFilters: Dispatch<SetStateAction<TagFilter | null>>;
suffixIcon?: React.ReactNode;
}
QueryBuilderSearchWrapper.defaultProps = {
suffixIcon: undefined,
};
export default QueryBuilderSearchWrapper;

View File

@@ -1,3 +0,0 @@
import { CSSProperties } from 'react';
export const rawLineStyle: CSSProperties = {};

View File

@@ -1,8 +0,0 @@
import { Button } from 'antd';
import styled from 'styled-components';
export const ButtonContainer = styled(Button)`
&&& {
padding-left: 0;
}
`;

View File

@@ -1,13 +0,0 @@
.custom-multiselect-dropdown {
.divider {
height: 1px;
background-color: #e8e8e8;
margin: 4px 0;
}
.all-option {
font-weight: 500;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 8px;
}
}

View File

@@ -1,19 +0,0 @@
.loading-panel-data {
padding: 24px 0;
height: 240px;
display: flex;
justify-content: center;
align-items: flex-start;
.loading-panel-data-content {
display: flex;
align-items: flex-start;
flex-direction: column;
.loading-gif {
height: 72px;
margin-left: -24px;
}
}
}

View File

@@ -1,17 +0,0 @@
import { Typography } from 'antd';
import loadingPlaneUrl from '@/assets/Icons/loading-plane.gif';
import './PanelDataLoading.styles.scss';
export function PanelDataLoading(): JSX.Element {
return (
<div className="loading-panel-data">
<div className="loading-panel-data-content">
<img className="loading-gif" src={loadingPlaneUrl} alt="wait-icon" />
<Typography.Text>Fetching data...</Typography.Text>
</div>
</div>
);
}

View File

@@ -87,7 +87,7 @@
input {
color: var(--l1-foreground);
font-size: var(--font-size-sm);
font-size: var(--font-size-xs);
}
.ant-picker-suffix {
@@ -126,12 +126,6 @@
}
&__copy-btn {
flex-shrink: 0;
height: 32px;
border-radius: 0 2px 2px 0;
border-top: none;
border-right: none;
border-bottom: none;
border-left: 1px solid var(--l1-border);
min-width: 40px;
}
@@ -152,6 +146,7 @@
color: var(--foreground);
letter-spacing: 0.48px;
text-transform: uppercase;
margin-bottom: var(--spacing-4);
}
&__footer {

View File

@@ -22,9 +22,8 @@ function KeyCreatedPhase({
<div className="add-key-modal__key-display">
<span className="add-key-modal__key-text">{createdKey.key}</span>
<Button
variant="outlined"
variant="link"
color="secondary"
size="sm"
onClick={onCopy}
className="add-key-modal__copy-btn"
>

View File

@@ -106,7 +106,7 @@ function KeyFormPhase({
<div className="add-key-modal__footer">
<div className="add-key-modal__footer-right">
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
<Button variant="solid" color="secondary" onClick={onClose}>
Cancel
</Button>
<Button
@@ -115,7 +115,6 @@ function KeyFormPhase({
form={FORM_ID}
variant="solid"
color="primary"
size="sm"
loading={isSubmitting}
disabled={!isValid}
>

View File

@@ -136,7 +136,7 @@ function EditKeyForm({
</form>
<div className="edit-key-modal__footer">
<Button variant="ghost" color="destructive" onClick={onRevokeClick}>
<Button variant="link" color="destructive" onClick={onRevokeClick}>
<Trash2 size={12} />
Revoke Key
</Button>

View File

@@ -119,7 +119,7 @@
input {
color: var(--l1-foreground);
font-size: 13px;
font-size: var(--font-size-xs);
}
.ant-picker-suffix {

View File

@@ -20,7 +20,7 @@ import { useErrorModal } from 'providers/ErrorModalProvider';
import { useTimezone } from 'providers/Timezone';
import APIError from 'types/api/error';
import { RevokeKeyContent } from '../RevokeKeyModal';
import { RevokeKeyFooter } from '../RevokeKeyModal';
import EditKeyForm from './EditKeyForm';
import type { FormValues } from './types';
import { DEFAULT_FORM_VALUES, ExpiryMode } from './types';
@@ -158,17 +158,25 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
}
width={isRevokeConfirmOpen ? 'narrow' : 'base'}
className={
isRevokeConfirmOpen ? 'alert-dialog delete-dialog' : 'edit-key-modal'
isRevokeConfirmOpen ? 'alert-dialog sa-delete-dialog' : 'edit-key-modal'
}
showCloseButton={!isRevokeConfirmOpen}
disableOutsideClick={isErrorModalVisible}
footer={
isRevokeConfirmOpen ? (
<RevokeKeyFooter
isRevoking={isRevoking}
onCancel={(): void => setIsRevokeConfirmOpen(false)}
onConfirm={handleRevoke}
/>
) : undefined
}
>
{isRevokeConfirmOpen ? (
<RevokeKeyContent
isRevoking={isRevoking}
onCancel={(): void => setIsRevokeConfirmOpen(false)}
onConfirm={handleRevoke}
/>
<>
Revoking this key will permanently invalidate it. Any systems using this
key will lose access immediately.
</>
) : (
<EditKeyForm
register={register}

View File

@@ -72,7 +72,6 @@ function OverviewTab({
id="sa-name"
value={localName}
onChange={(e): void => onNameChange(e.target.value)}
className="sa-drawer__input"
placeholder="Enter name"
/>
)}

View File

@@ -17,39 +17,32 @@ import { parseAsString, useQueryState } from 'nuqs';
import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
export interface RevokeKeyContentProps {
export interface RevokeKeyFooterProps {
isRevoking: boolean;
onCancel: () => void;
onConfirm: () => void;
}
export function RevokeKeyContent({
export function RevokeKeyFooter({
isRevoking,
onCancel,
onConfirm,
}: RevokeKeyContentProps): JSX.Element {
}: RevokeKeyFooterProps): JSX.Element {
return (
<>
<p className="delete-dialog__body">
Revoking this key will permanently invalidate it. Any systems using this key
will lose access immediately.
</p>
<div className="delete-dialog__footer">
<Button variant="solid" color="secondary" size="sm" onClick={onCancel}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
size="sm"
loading={isRevoking}
onClick={onConfirm}
>
<Trash2 size={12} />
Revoke Key
</Button>
</div>
<Button variant="solid" color="secondary" onClick={onCancel}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
loading={isRevoking}
onClick={onConfirm}
>
<Trash2 size={12} />
Revoke Key
</Button>
</>
);
}
@@ -112,15 +105,19 @@ function RevokeKeyModal(): JSX.Element {
}}
title={`Revoke ${keyName ?? 'key'}?`}
width="narrow"
className="alert-dialog delete-dialog"
className="alert-dialog sa-delete-dialog"
showCloseButton={false}
disableOutsideClick={isErrorModalVisible}
footer={
<RevokeKeyFooter
isRevoking={isRevoking}
onCancel={handleCancel}
onConfirm={handleConfirm}
/>
}
>
<RevokeKeyContent
isRevoking={isRevoking}
onCancel={handleCancel}
onConfirm={handleConfirm}
/>
Revoking this key will permanently invalidate it. Any systems using this key
will lose access immediately.
</DialogWrapper>
);
}

View File

@@ -57,6 +57,8 @@
color: var(--l1-foreground);
}
}
min-width: 220px;
}
&__tab {
@@ -166,18 +168,6 @@
cursor: default;
}
&__input {
height: 32px;
background: var(--l2-background);
border-color: var(--l1-border);
color: var(--l1-foreground);
box-shadow: none;
&::placeholder {
color: var(--l3-foreground);
}
}
&__input-wrapper {
display: flex;
align-items: center;
@@ -186,7 +176,7 @@
padding: 0 var(--padding-2);
border-radius: 2px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border: 1px solid var(--border);
&--disabled {
cursor: not-allowed;
@@ -195,8 +185,8 @@
}
&__input-text {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
line-height: var(--line-height-18);
letter-spacing: -0.07px;

View File

@@ -129,7 +129,7 @@ function ServiceAccountDrawer({
useEffect(() => {
if (account?.id) {
setLocalName(account?.name ?? '');
setKeysPage(1);
void setKeysPage(1);
}
}, [account?.id, account?.name, setKeysPage]);
@@ -176,7 +176,7 @@ function ServiceAccountDrawer({
}
const maxPage = Math.max(1, Math.ceil(keys.length / PAGE_SIZE));
if (keysPage > maxPage) {
setKeysPage(maxPage);
void setKeysPage(maxPage);
}
}, [keysLoading, keys.length, keysPage, setKeysPage]);
@@ -214,8 +214,8 @@ function ServiceAccountDrawer({
data: { name: localName },
});
setSaveErrors((prev) => prev.filter((e) => e.context !== 'Name update'));
refetchAccount();
queryClient.invalidateQueries(getListServiceAccountsQueryKey());
void refetchAccount();
void queryClient.invalidateQueries(getListServiceAccountsQueryKey());
} catch (err) {
setSaveErrors((prev) =>
prev.map((e) =>
@@ -337,8 +337,8 @@ function ServiceAccountDrawer({
onSuccess({ closeDrawer: false });
}
refetchAccount();
queryClient.invalidateQueries(getListServiceAccountsQueryKey());
void refetchAccount();
void queryClient.invalidateQueries(getListServiceAccountsQueryKey());
} finally {
setIsSaving(false);
}
@@ -357,12 +357,12 @@ function ServiceAccountDrawer({
]);
const handleClose = useCallback((): void => {
setIsDeleteOpen(null);
setIsAddKeyOpen(null);
setSelectedAccountId(null);
setActiveTab(null);
setKeysPage(null);
setEditKeyId(null);
void setIsDeleteOpen(null);
void setIsAddKeyOpen(null);
void setSelectedAccountId(null);
void setActiveTab(null);
void setKeysPage(null);
void setEditKeyId(null);
setSaveErrors([]);
}, [
setSelectedAccountId,
@@ -379,12 +379,13 @@ function ServiceAccountDrawer({
<ToggleGroup
type="single"
value={activeTab}
size="sm"
onChange={(val): void => {
if (val) {
setActiveTab(val as ServiceAccountDrawerTab);
void setActiveTab(val as ServiceAccountDrawerTab);
if (val !== ServiceAccountDrawerTab.Keys) {
setKeysPage(null);
setEditKeyId(null);
void setKeysPage(null);
void setEditKeyId(null);
}
}
}}
@@ -415,7 +416,7 @@ function ServiceAccountDrawer({
color="secondary"
disabled={isDeleted}
onClick={(): void => {
setIsAddKeyOpen(true);
void setIsAddKeyOpen(true);
}}
>
<Plus size={12} />
@@ -503,7 +504,7 @@ function ServiceAccountDrawer({
variant="link"
color="destructive"
onClick={(): void => {
setIsDeleteOpen(true);
void setIsDeleteOpen(true);
}}
>
<Trash2 size={12} />
@@ -512,7 +513,7 @@ function ServiceAccountDrawer({
)}
{!isDeleted && (
<div className="sa-drawer__footer-right">
<Button variant="solid" color="secondary" onClick={handleClose}>
<Button variant="outlined" color="secondary" onClick={handleClose}>
<X size={14} />
Cancel
</Button>

View File

@@ -1,41 +0,0 @@
import { css, FlattenSimpleInterpolation } from 'styled-components';
const cssProperty = (key: any, value: any): FlattenSimpleInterpolation =>
key &&
value &&
css`
${key}: ${value};
`;
interface IFlexProps {
flexDirection?: string; // Need to replace this with exact css props. Not able to find any :(
flex?: number | string;
}
export const Flex = ({
flexDirection,
flex,
}: IFlexProps): FlattenSimpleInterpolation => css`
${cssProperty('flex-direction', flexDirection)}
${cssProperty('flex', flex)}
`;
interface IDisplayProps {
display?: string;
}
export const Display = ({
display,
}: IDisplayProps): FlattenSimpleInterpolation => css`
${cssProperty('display', display)}
`;
interface ISpacingProps {
margin?: string;
padding?: string;
}
export const Spacing = ({
margin,
padding,
}: ISpacingProps): FlattenSimpleInterpolation => css`
${cssProperty('margin', margin)}
${cssProperty('padding', padding)}
`;

View File

@@ -1,5 +0,0 @@
export type TabLabelProps = {
isDisabled: boolean;
label: string;
tooltipText?: string;
};

View File

@@ -1,29 +0,0 @@
import { memo } from 'react';
import { Tooltip } from 'antd';
import { TabLabelProps } from './TabLabel.interfaces';
function TabLabel({
label,
isDisabled,
tooltipText,
}: TabLabelProps): JSX.Element {
const currentLabel = <span data-testid={`${label}`}>{label}</span>;
if (isDisabled) {
return (
<Tooltip
trigger="hover"
autoAdjustOverflow
placement="top"
title={tooltipText}
>
{currentLabel}
</Tooltip>
);
}
return currentLabel;
}
export default memo(TabLabel);

View File

@@ -1,5 +0,0 @@
.tab-title {
display: flex;
gap: 4px;
align-items: center;
}

View File

@@ -1,41 +0,0 @@
import { useState } from 'react';
import { Radio } from 'antd';
import type { RadioChangeEvent } from 'antd/lib';
import { History, Table } from 'lucide-react';
import { ALERT_TABS } from '../constants';
import './Tabs.styles.scss';
export function Tabs(): JSX.Element {
const [selectedTab, setSelectedTab] = useState('overview');
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedTab(e.target.value);
};
return (
<Radio.Group className="tabs" onChange={handleTabChange} value={selectedTab}>
<Radio.Button
className={
selectedTab === ALERT_TABS.OVERVIEW ? 'selected_view tab' : 'tab'
}
value={ALERT_TABS.OVERVIEW}
>
<div className="tab-title">
<Table size={14} />
Overview
</div>
</Radio.Button>
<Radio.Button
className={selectedTab === ALERT_TABS.HISTORY ? 'selected_view tab' : 'tab'}
value={ALERT_TABS.HISTORY}
>
<div className="tab-title">
<History size={14} />
History
</div>
</Radio.Button>
</Radio.Group>
);
}

View File

@@ -1,18 +0,0 @@
@mixin flex-center {
display: flex;
justify-content: space-between;
align-items: center;
}
.tabs-and-filters {
@include flex-center;
margin-top: 1rem;
margin-bottom: 1rem;
.filters {
@include flex-center;
gap: 16px;
.reset-button {
@include flex-center;
}
}
}

View File

@@ -1,16 +0,0 @@
import { Filters } from 'components/AlertDetailsFilters/Filters';
import { Tabs } from './Tabs/Tabs';
import './TabsAndFilters.styles.scss';
function TabsAndFilters(): JSX.Element {
return (
<div className="tabs-and-filters">
<Tabs />
<Filters />
</div>
);
}
export default TabsAndFilters;

View File

@@ -1,5 +0,0 @@
export const ALERT_TABS = {
OVERVIEW: 'OVERVIEW',
HISTORY: 'HISTORY',
ACTIVITY: 'ACTIVITY',
} as const;

View File

@@ -1,73 +0,0 @@
import { useMemo } from 'react';
import type { ColumnSizingState } from '@tanstack/react-table';
import { Skeleton } from 'antd';
import { TableColumnDef } from './types';
import { getColumnWidthStyle } from './utils';
import tableStyles from './TanStackTable.module.scss';
import styles from './TanStackTableSkeleton.module.scss';
type TanStackTableSkeletonProps<TData> = {
columns: TableColumnDef<TData>[];
rowCount: number;
isDarkMode: boolean;
columnSizing?: ColumnSizingState;
};
export function TanStackTableSkeleton<TData>({
columns,
rowCount,
isDarkMode,
columnSizing,
}: TanStackTableSkeletonProps<TData>): JSX.Element {
const rows = useMemo(
() => Array.from({ length: rowCount }, (_, i) => i),
[rowCount],
);
return (
<table className={tableStyles.tanStackTable}>
<colgroup>
{columns.map((column, index) => (
<col
key={column.id}
style={getColumnWidthStyle(
column,
columnSizing?.[column.id],
index === columns.length - 1,
)}
/>
))}
</colgroup>
<thead>
<tr>
{columns.map((column) => (
<th
key={column.id}
className={tableStyles.tableHeaderCell}
data-dark-mode={isDarkMode}
>
{typeof column.header === 'function' ? (
<Skeleton.Input active size="small" className={styles.headerSkeleton} />
) : (
column.header
)}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((rowIndex) => (
<tr key={rowIndex} className={tableStyles.tableRow}>
{columns.map((column) => (
<td key={column.id} className={tableStyles.tableCell}>
<Skeleton.Input active size="small" className={styles.cellSkeleton} />
</td>
))}
</tr>
))}
</tbody>
</table>
);
}

View File

@@ -1,13 +0,0 @@
import styled from 'styled-components';
interface TextContainerProps {
noButtonMargin?: boolean;
}
export const TextContainer = styled.div<TextContainerProps>`
display: flex;
> button {
margin-left: ${({ noButtonMargin }): string =>
noButtonMargin ? '0' : '0.5rem'}
`;

View File

@@ -1,42 +0,0 @@
import { ReactChild } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, Space, Typography } from 'antd';
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
import { Container, LeftContainer, Logo } from './styles';
const { Title } = Typography;
function WelcomeLeftContainer({
version,
children,
}: WelcomeLeftContainerProps): JSX.Element {
const { t } = useTranslation();
return (
<Container>
<LeftContainer direction="vertical">
<Space align="center">
<Logo src={signozBrandLogoUrl} alt="logo" />
<Title style={{ fontSize: '46px', margin: 0 }}>SigNoz</Title>
</Space>
<Typography>{t('monitor_signup')}</Typography>
<Card
style={{ width: 'max-content' }}
bodyStyle={{ padding: '1px 8px', width: '100%' }}
>
SigNoz {version}
</Card>
</LeftContainer>
{children}
</Container>
);
}
interface WelcomeLeftContainerProps {
version: string;
children: ReactChild;
}
export default WelcomeLeftContainer;

View File

@@ -1,23 +0,0 @@
import { Space } from 'antd';
import styled from 'styled-components';
export const LeftContainer = styled(Space)`
flex: 1;
`;
export const Logo = styled.img`
width: 60px;
`;
export const Container = styled.div`
&&& {
display: flex;
justify-content: center;
gap: 16px;
align-items: center;
min-height: 100vh;
max-width: 1024px;
margin: 0 auto;
}
`;

View File

@@ -1,9 +0,0 @@
import {
AlertRuleTimelineTableResponse,
AlertRuleTimelineTableResponsePayload,
} from 'types/api/alerts/def';
export type TimelineTableProps = {
timelineData: AlertRuleTimelineTableResponse[];
totalItems: AlertRuleTimelineTableResponsePayload['data']['total'];
};

View File

@@ -1,2 +0,0 @@
// setting to 25 hours because we want to display the horizontal graph when the user selects 'Last 1 day' from date and time selector
export const HORIZONTAL_GRAPH_HOURS_THRESHOLD = 25;

View File

@@ -1,42 +0,0 @@
import { UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import noDataUrl from '@/assets/Icons/no-data.svg';
import EndPointsDropDown from './EndPointsDropDown';
function EndPointDetailsZeroState({
setSelectedEndPointName,
endPointDropDownDataQuery,
}: {
setSelectedEndPointName: (endPointName: string) => void;
endPointDropDownDataQuery: UseQueryResult<SuccessResponse<any>>;
}): JSX.Element {
return (
<div className="end-point-details-zero-state-wrapper">
<div className="end-point-details-zero-state-content">
<img
src={noDataUrl}
alt="no-data"
width={32}
height={32}
className="end-point-details-zero-state-icon"
/>
<div className="end-point-details-zero-state-content-wrapper">
<div className="end-point-details-zero-state-text-content">
<div className="title">No endpoint selected yet</div>
<div className="description">Select an endpoint to see the details</div>
</div>
<EndPointsDropDown
setSelectedEndPointName={setSelectedEndPointName}
endPointDropDownDataQuery={endPointDropDownDataQuery}
parentContainerDiv=".end-point-details-zero-state-wrapper"
dropdownStyle={{ width: '60%' }}
/>
</div>
</div>
</div>
);
}
export default EndPointDetailsZeroState;

View File

@@ -1,136 +0,0 @@
import { useMemo } from 'react';
import { useQueries } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { LoadingOutlined } from '@ant-design/icons';
import { Spin, Table } from 'antd';
import type { ColumnType } from 'antd/lib/table';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
createFiltersForSelectedRowData,
EndPointsTableRowData,
formatEndPointsDataForTable,
getEndPointsColumnsConfig,
getEndPointsQueryPayload,
} from 'container/ApiMonitoring/utils';
import LoadingContainer from 'container/InfraMonitoringK8s/LoadingContainer';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import { VIEW_TYPES, VIEWS } from '../constants';
function ExpandedRow({
domainName,
selectedRowData,
setSelectedEndPointName,
setSelectedView,
orderBy,
}: {
domainName: string;
selectedRowData: EndPointsTableRowData;
setSelectedEndPointName: (name: string) => void;
setSelectedView: (view: VIEWS) => void;
orderBy: OrderByPayload | null;
}): JSX.Element {
const nestedColumns = useMemo(() => getEndPointsColumnsConfig(false, []), []);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const groupedByRowDataQueryPayload = useMemo(() => {
if (!selectedRowData) {
return null;
}
const filters = createFiltersForSelectedRowData(selectedRowData);
const baseQueryPayload = getEndPointsQueryPayload(
[],
domainName,
Math.floor(minTime / 1e9),
Math.floor(maxTime / 1e9),
);
return baseQueryPayload.map((currentQueryPayload) => ({
...currentQueryPayload,
query: {
...currentQueryPayload.query,
builder: {
...currentQueryPayload.query.builder,
queryData: currentQueryPayload.query.builder.queryData.map(
(queryData) => ({
...queryData,
filters: {
items: [...(queryData.filters?.items || []), ...(filters?.items || [])],
op: 'AND',
},
}),
),
},
},
}));
}, [domainName, minTime, maxTime, selectedRowData]);
const groupedByRowQueries = useQueries(
groupedByRowDataQueryPayload
? groupedByRowDataQueryPayload.map((payload) => ({
queryKey: [
`${REACT_QUERY_KEY.GET_NESTED_ENDPOINTS_LIST}-${domainName}-${selectedRowData?.key}`,
payload,
ENTITY_VERSION_V4,
selectedRowData?.key,
],
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
enabled: !!payload && !!selectedRowData,
}))
: [],
);
const groupedByRowQuery = groupedByRowQueries[0];
return (
<div className="expanded-table-container">
{groupedByRowQuery?.isFetching || groupedByRowQuery?.isLoading ? (
<LoadingContainer />
) : (
<div className="expanded-table">
<Table
columns={nestedColumns as ColumnType<EndPointsTableRowData>[]}
dataSource={
groupedByRowQuery?.data
? formatEndPointsDataForTable(
groupedByRowQuery.data?.payload.data.result[0].table?.rows,
[],
orderBy,
)
: []
}
pagination={false}
scroll={{ x: true }}
tableLayout="fixed"
showHeader={false}
loading={{
spinning: groupedByRowQuery?.isFetching || groupedByRowQuery?.isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
onRow={(record): { onClick: () => void; className: string } => ({
onClick: (): void => {
setSelectedEndPointName(record.endpointName);
setSelectedView(VIEW_TYPES.ENDPOINT_STATS);
logEvent('API Monitoring: Endpoint name row clicked', {});
},
className: 'expanded-clickable-row',
})}
/>
</div>
)}
</div>
);
}
export default ExpandedRow;

View File

@@ -1,33 +0,0 @@
import { PureComponent } from 'react';
interface State {
hasError: boolean;
}
interface Props {
children: JSX.Element;
}
class ErrorLink extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): State {
return { hasError: true };
}
render(): JSX.Element {
const { children } = this.props;
const { hasError } = this.state;
if (hasError) {
return <div />;
}
return children;
}
}
export default ErrorLink;

View File

@@ -1,23 +0,0 @@
import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
const isInternalLink = href.startsWith('/');
if (isInternalLink) {
return <Link to={href}>{children}</Link>;
}
return (
<a rel="noreferrer" target="_blank" href={href}>
{children}
</a>
);
}
interface LinkContainerProps {
children: ReactNode;
href: string;
}
export default LinkContainer;

View File

@@ -1,49 +0,0 @@
import { lazy, Suspense, useMemo } from 'react';
import { Menu, Space } from 'antd';
import Spinner from 'components/Spinner';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
import { lazyRetry } from 'utils/lazyWithRetries';
import ErrorLink from './ErrorLink';
import LinkContainer from './Link';
function HelpToolTip({ config }: HelpToolTipProps): JSX.Element {
const sortedConfig = useMemo(
() => config.components.sort((a, b) => a.position - b.position),
[config.components],
);
const isDarkMode = useIsDarkMode();
const items = sortedConfig.map((item) => {
const iconName = `${isDarkMode ? item.darkIcon : item.lightIcon}`;
const Component = lazy(() =>
lazyRetry(() => import(`@ant-design/icons/es/icons/${iconName}.js`)),
);
return {
key: item.text + item.href,
label: (
<ErrorLink key={item.text + item.href}>
<Suspense fallback={<Spinner height="5vh" />}>
<LinkContainer href={item.href}>
<Space size="small" align="start">
<Component />
{item.text}
</Space>
</LinkContainer>
</Suspense>
</ErrorLink>
),
};
});
return <Menu items={items} />;
}
interface HelpToolTipProps {
config: ConfigProps;
}
export default HelpToolTip;

View File

@@ -1,76 +0,0 @@
import { useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import {
CaretDownFilled,
CaretUpFilled,
QuestionCircleFilled,
QuestionCircleOutlined,
} from '@ant-design/icons';
import { Space } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { AppState } from 'store/reducers';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
import AppReducer from 'types/reducer/app';
import HelpToolTip from './Config';
import { ConfigDropdown } from './styles';
function DynamicConfigDropdown({
frontendId,
}: DynamicConfigDropdownProps): JSX.Element {
const { configs } = useSelector<AppState, AppReducer>((state) => state.app);
const isDarkMode = useIsDarkMode();
const [isHelpDropDownOpen, setIsHelpDropDownOpen] = useState<boolean>(false);
const config = useMemo(
() =>
Object.values(configs).find(
(config) => config.frontendPositionId === frontendId,
),
[frontendId, configs],
);
const onToggleHandler = (): void => {
setIsHelpDropDownOpen(!isHelpDropDownOpen);
};
const menu = useMemo(
() => ({
items: [
{
key: '1',
label: <HelpToolTip config={config as ConfigProps} />,
},
],
}),
[config],
);
if (!config) {
return <div />;
}
const Icon = isDarkMode ? QuestionCircleOutlined : QuestionCircleFilled;
const DropDownIcon = isHelpDropDownOpen ? CaretUpFilled : CaretDownFilled;
return (
<ConfigDropdown
onOpenChange={onToggleHandler}
trigger={['click']}
menu={menu}
open={isHelpDropDownOpen}
>
<Space align="center">
<Icon style={{ fontSize: 26, color: 'white', paddingTop: 26 }} />
<DropDownIcon style={{ color: 'white' }} />
</Space>
</ConfigDropdown>
);
}
interface DynamicConfigDropdownProps {
frontendId: string;
}
export default DynamicConfigDropdown;

View File

@@ -1,6 +0,0 @@
import { Dropdown } from 'antd';
import styled from 'styled-components';
export const ConfigDropdown = styled(Dropdown)`
cursor: pointer;
`;

View File

@@ -1,3 +0,0 @@
import EvaluationSettings from './EvaluationSettings';
export default EvaluationSettings;

View File

@@ -78,6 +78,7 @@
display: flex;
align-items: center;
gap: var(--spacing-10);
padding-left: 18px;
}
.custom-domain-card-meta-row.workspace-name-hidden {
@@ -124,30 +125,6 @@
}
}
.workspace-url-trigger {
display: inline-flex;
align-items: center;
gap: var(--spacing-3);
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--l1-foreground);
font-size: var(--font-size-xs);
line-height: var(--line-height-18);
letter-spacing: -0.06px;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
svg {
flex-shrink: 0;
color: var(--l2-foreground);
}
}
.workspace-url-dropdown {
border-radius: 4px;
border: 1px solid var(--l1-border);

View File

@@ -204,6 +204,7 @@ export default function CustomDomainSettings(): JSX.Element {
>
<Dropdown
trigger={['click']}
disabled={isFetchingHosts}
dropdownRender={(): JSX.Element => (
<div className="workspace-url-dropdown">
<span className="workspace-url-dropdown-header">
@@ -239,12 +240,7 @@ export default function CustomDomainSettings(): JSX.Element {
</div>
)}
>
<Button
className="workspace-url-trigger"
disabled={isFetchingHosts}
variant="link"
color="none"
>
<Button variant="link" color="none">
<Link2 size={12} />
<span>{stripProtocol(activeHost?.url ?? '')}</span>
<ChevronDown size={12} />

View File

@@ -1,27 +0,0 @@
import { ChangeEvent, Dispatch, SetStateAction, useCallback } from 'react';
import Input from 'components/Input';
function DashboardName({ setName, name }: DashboardNameProps): JSX.Element {
const onChangeHandler = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
},
[setName],
);
return (
<Input
size="middle"
placeholder="Title"
value={name}
onChangeHandler={onChangeHandler}
/>
);
}
interface DashboardNameProps {
name: string;
setName: Dispatch<SetStateAction<string>>;
}
export default DashboardName;

View File

@@ -1,100 +0,0 @@
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use';
import { CopyFilled, DownloadOutlined } from '@ant-design/icons';
import { Button, Modal } from 'antd';
import Editor from 'components/Editor';
import { useNotifications } from 'hooks/useNotifications';
import { DashboardData } from 'types/api/dashboard/getAll';
import { downloadObjectAsJson } from './utils';
function ShareModal({
isJSONModalVisible,
onToggleHandler,
selectedData,
}: ShareModalProps): JSX.Element {
const getParsedValue = (): string => JSON.stringify(selectedData, null, 2);
const [jsonValue, setJSONValue] = useState<string>(getParsedValue());
const { t } = useTranslation(['dashboard', 'common']);
const [state, setCopy] = useCopyToClipboard();
const { notifications } = useNotifications();
useEffect(() => {
if (state.error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
if (state.value) {
notifications.success({
message: t('success', {
ns: 'common',
}),
});
}
}, [state.error, state.value, t, notifications]);
const GetFooterComponent = useMemo(() => {
return (
<>
<Button
style={{
marginTop: '16px',
}}
onClick={(): void => setCopy(jsonValue)}
type="primary"
size="small"
>
<CopyFilled /> {t('copy_to_clipboard')}
</Button>
<Button
type="primary"
size="small"
onClick={(): void => {
downloadObjectAsJson(selectedData, selectedData.title);
}}
>
<DownloadOutlined /> {t('download_json')}
</Button>
</>
);
}, [jsonValue, selectedData, setCopy, t]);
return (
<Modal
open={isJSONModalVisible}
onCancel={(): void => {
onToggleHandler();
}}
width="80vw"
centered
title={t('share', {
ns: 'common',
})}
okText={t('download_json')}
cancelText={t('cancel')}
destroyOnClose
footer={GetFooterComponent}
>
<Editor
height="70vh"
onChange={(value): void => setJSONValue(value)}
value={jsonValue}
/>
</Modal>
);
}
interface ShareModalProps {
isJSONModalVisible: boolean;
onToggleHandler: VoidFunction;
selectedData: DashboardData;
}
export default ShareModal;

View File

@@ -1,35 +0,0 @@
import { ChangeEvent, Dispatch, SetStateAction, useCallback } from 'react';
import { Input } from 'antd';
import { Container } from './styles';
const { TextArea } = Input;
function Description({
description,
setDescription,
}: DescriptionProps): JSX.Element {
const onChangeHandler = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
setDescription(e.target.value);
},
[setDescription],
);
return (
<Container>
<TextArea
placeholder="Description of the dashboard"
onChange={onChangeHandler}
value={description}
/>
</Container>
);
}
interface DescriptionProps {
description: string;
setDescription: Dispatch<SetStateAction<string>>;
}
export default Description;

View File

@@ -1,5 +0,0 @@
import styled from 'styled-components';
export const Container = styled.div`
margin-top: 1rem;
`;

View File

@@ -104,7 +104,12 @@ export const usePanelContextMenu = ({
}
if (data && data?.record?.queryName) {
onClick(data.coord, { ...data.record, label: data.label, timeRange });
onClick(data.coord, {
...data.record,
label: data.label,
seriesColor: data.seriesColor,
timeRange,
});
}
},
[onClick, queryResponse],

View File

@@ -14,6 +14,7 @@ import { usePanelContextMenu } from '../../hooks/usePanelContextMenu';
import { prepareBarPanelConfig, prepareBarPanelData } from './utils';
import '../Panel.styles.scss';
import get from 'lodash/get';
function BarPanel(props: PanelWrapperProps): JSX.Element {
const {
@@ -114,7 +115,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
}, []);
const groupBy = useMemo(() => {
return widget.query.builder.queryData[0].groupBy;
return get(widget, 'query.builder.queryData[0].groupBy', []);
}, [widget.query]);
return (

View File

@@ -10,6 +10,7 @@ import { ContextMenu } from 'periscope/components/ContextMenu';
import { useTimezone } from 'providers/Timezone';
import uPlot from 'uplot';
import { getTimeRange } from 'utils/getTimeRange';
import get from 'lodash/get';
import { prepareChartData, prepareUPlotConfig } from '../TimeSeriesPanel/utils';
@@ -105,7 +106,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
]);
const groupBy = useMemo(() => {
return widget.query.builder.queryData[0].groupBy;
return get(widget, 'query.builder.queryData[0].groupBy', []);
}, [widget.query]);
return (

View File

@@ -1,59 +0,0 @@
.download-logs-popover {
.ant-popover-inner {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
padding: 12px 18px 12px 14px;
.download-logs-content {
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-start;
.action-btns {
padding: 4px 0px !important;
width: 159px;
display: flex;
align-items: center;
color: var(--l2-foreground);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
gap: 6px;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
.action-btns:hover {
&.ant-btn-text {
background-color: color-mix(
in srgb,
var(--bg-robin-200) 4%,
transparent
) !important;
}
}
.export-heading {
color: var(--muted-foreground);
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.88px;
text-transform: uppercase;
}
}
}
}

View File

@@ -1,94 +0,0 @@
import { useState } from 'react';
import { Button, Popover, Typography } from 'antd';
import { FileDigit, FileDown, Sheet } from 'lucide-react';
import { unparse } from 'papaparse';
import { DownloadProps } from './DownloadV2.types';
import './DownloadV2.styles.scss';
function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
const [isDownloading, setIsDownloading] = useState(false);
const downloadExcelFile = async (): Promise<void> => {
setIsDownloading(true);
try {
const headers = Object.keys(Object.assign({}, ...data)).map((item) => {
const updatedTitle = item
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
return {
title: updatedTitle,
dataIndex: item,
};
});
const excelLib = await import('antd-table-saveas-excel');
const excel = new excelLib.Excel();
excel
.addSheet(fileName)
.addColumns(headers)
.addDataSource(data, {
str2Percent: true,
})
.saveAs(`${fileName}.xlsx`);
} finally {
setIsDownloading(false);
}
};
const downloadCsvFile = (): void => {
const csv = unparse(data);
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const csvUrl = URL.createObjectURL(csvBlob);
const downloadLink = document.createElement('a');
downloadLink.href = csvUrl;
downloadLink.download = `${fileName}.csv`;
downloadLink.click();
downloadLink.remove();
};
return (
<Popover
trigger={['click']}
placement="bottomRight"
rootClassName="download-logs-popover"
arrow={false}
content={
<div className="download-logs-content">
<Typography.Text className="export-heading">Export As</Typography.Text>
<Button
icon={<Sheet size={14} />}
type="text"
onClick={downloadExcelFile}
className="action-btns"
loading={isDownloading}
>
Excel (.xlsx)
</Button>
<Button
icon={<FileDigit size={14} />}
type="text"
onClick={downloadCsvFile}
className="action-btns"
>
CSV
</Button>
</div>
}
>
<Button
className="periscope-btn ghost"
loading={isLoading}
icon={<FileDown size={14} />}
/>
</Popover>
);
}
Download.defaultProps = {
isLoading: undefined,
};
export default Download;

View File

@@ -1,10 +0,0 @@
export type DownloadOptions = {
isDownloadEnabled: boolean;
fileName: string;
};
export type DownloadProps = {
data: Record<string, string>[];
isLoading?: boolean;
fileName: string;
};

View File

@@ -1,11 +0,0 @@
import styled from 'styled-components';
export const ButtonContainer = styled.div`
&&& {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 2rem;
}
`;

View File

@@ -1,8 +0,0 @@
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
export type ExplorerControlPanelProps = {
selectedOptionFormat: string;
isShowPageSize: boolean;
isLoading: boolean;
optionsMenuConfig?: OptionsMenuConfig;
};

View File

@@ -1,33 +0,0 @@
import { Col, Row } from 'antd';
import OptionsMenu from 'container/OptionsMenu';
import PageSizeSelect from 'container/PageSizeSelect';
import { ExplorerControlPanelProps } from './ExplorerControlPanel.interfaces';
import { ContainerStyled } from './styles';
function ExplorerControlPanel({
selectedOptionFormat,
isLoading,
isShowPageSize,
optionsMenuConfig,
}: ExplorerControlPanelProps): JSX.Element {
return (
<ContainerStyled>
<Row justify="end" gutter={30}>
{optionsMenuConfig && (
<Col>
<OptionsMenu
selectedOptionFormat={selectedOptionFormat}
config={optionsMenuConfig}
/>
</Col>
)}
<Col>
<PageSizeSelect isLoading={isLoading} isShow={isShowPageSize} />
</Col>
</Row>
</ContainerStyled>
);
}
export default ExplorerControlPanel;

View File

@@ -1,5 +0,0 @@
import styled from 'styled-components';
export const ContainerStyled = styled.div`
margin-bottom: 0.3rem;
`;

View File

@@ -1,61 +0,0 @@
import { Dispatch, SetStateAction } from 'react';
import { Form, Input, Select } from 'antd';
import { LabelFilterStatement } from 'container/CreateAlertChannels/config';
const { Option } = Select;
// LabelFilterForm supports filters or matchers on alert notifications
// presently un-used but will be introduced to the channel creation at some
// point
function LabelFilterForm({ setFilter }: LabelFilterProps): JSX.Element {
return (
<Form.Item name="label_filter" label="Notify When (Optional)">
<Input.Group compact>
<Select
defaultValue="Severity"
style={{ width: '15%' }}
onChange={(event): void => {
setFilter((value) => {
const first: LabelFilterStatement = value[0] as LabelFilterStatement;
first.name = event;
return [first];
});
}}
>
<Option value="severity">Severity</Option>
<Option value="service">Service</Option>
</Select>
<Select
defaultValue="="
onChange={(event): void => {
setFilter((value) => {
const first: LabelFilterStatement = value[0] as LabelFilterStatement;
first.comparator = event;
return [first];
});
}}
>
<Option value="=">=</Option>
<Option value="!=">!=</Option>
</Select>
<Input
style={{ width: '20%' }}
placeholder="enter a text here"
onChange={(event): void => {
setFilter((value) => {
const first: LabelFilterStatement = value[0] as LabelFilterStatement;
first.value = event.target.value;
return [first];
});
}}
/>
</Input.Group>
</Form.Item>
);
}
export interface LabelFilterProps {
setFilter: Dispatch<SetStateAction<Partial<Array<LabelFilterStatement>>>>;
}
export default LabelFilterForm;

View File

@@ -1,164 +0,0 @@
import { Trans, useTranslation } from 'react-i18next';
import { Col, Row, Typography } from 'antd';
import TextToolTip from 'components/TextToolTip';
import { EQueryType } from 'types/common/dashboard';
import {
StyledList,
StyledListItem,
StyledMainContainer,
StyledTopic,
} from './styles';
function UserGuide({ queryType }: UserGuideProps): JSX.Element {
// init namespace for translations
const { t } = useTranslation('alerts');
const renderStep1QB = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_qb_step1')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_qb_step1a')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step1b')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step1c')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step1d')}</StyledListItem>
</StyledList>
</>
);
const renderStep2QB = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_qb_step2')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_qb_step2a')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step2b')}</StyledListItem>
</StyledList>
</>
);
const renderStep3QB = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_qb_step3')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_qb_step3a')}</StyledListItem>
<StyledListItem>{t('user_guide_qb_step3b')}</StyledListItem>
</StyledList>
</>
);
const renderGuideForQB = (): JSX.Element => (
<>
{renderStep1QB()}
{renderStep2QB()}
{renderStep3QB()}
</>
);
const renderStep1PQL = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_pql_step1')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_pql_step1a')}</StyledListItem>
<StyledListItem>{t('user_guide_pql_step1b')}</StyledListItem>
</StyledList>
</>
);
const renderStep2PQL = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_pql_step2')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_pql_step2a')}</StyledListItem>
<StyledListItem>{t('user_guide_pql_step2b')}</StyledListItem>
</StyledList>
</>
);
const renderStep3PQL = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_pql_step3')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_pql_step3a')}</StyledListItem>
<StyledListItem>{t('user_guide_pql_step3b')}</StyledListItem>
</StyledList>
</>
);
const renderGuideForPQL = (): JSX.Element => (
<>
{renderStep1PQL()}
{renderStep2PQL()}
{renderStep3PQL()}
</>
);
const renderStep1CH = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_ch_step1')}</StyledTopic>
<StyledList>
<StyledListItem>
<Trans
i18nKey="user_guide_ch_step1a"
t={t}
components={[
<a
key={1}
target="_blank"
href=" https://signoz.io/docs/tutorial/writing-clickhouse-queries-in-dashboard/?utm_source=frontend&utm_medium=product&utm_id=alerts</>"
/>,
]}
/>
</StyledListItem>
<StyledListItem>{t('user_guide_ch_step1b')}</StyledListItem>
</StyledList>
</>
);
const renderStep2CH = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_ch_step2')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_ch_step2a')}</StyledListItem>
<StyledListItem>{t('user_guide_ch_step2b')}</StyledListItem>
</StyledList>
</>
);
const renderStep3CH = (): JSX.Element => (
<>
<StyledTopic>{t('user_guide_ch_step3')}</StyledTopic>
<StyledList>
<StyledListItem>{t('user_guide_ch_step3a')}</StyledListItem>
<StyledListItem>{t('user_guide_ch_step3b')}</StyledListItem>
</StyledList>
</>
);
const renderGuideForCH = (): JSX.Element => (
<>
{renderStep1CH()}
{renderStep2CH()}
{renderStep3CH()}
</>
);
return (
<StyledMainContainer>
<Row>
<Col flex="auto">
<Typography.Paragraph> {t('user_guide_headline')} </Typography.Paragraph>
</Col>
<Col flex="none">
<TextToolTip
text={t('user_tooltip_more_help')}
url="https://signoz.io/docs/userguide/alerts-management/?utm_source=product&utm_medium=create-alert#creating-a-new-alert-in-signoz"
/>
</Col>
</Row>
{queryType === EQueryType.QUERY_BUILDER && renderGuideForQB()}
{queryType === EQueryType.PROM && renderGuideForPQL()}
{queryType === EQueryType.CLICKHOUSE && renderGuideForCH()}
</StyledMainContainer>
);
}
interface UserGuideProps {
queryType: EQueryType;
}
export default UserGuide;

View File

@@ -1,17 +0,0 @@
import { Card, Typography } from 'antd';
import styled from 'styled-components';
export const StyledMainContainer = styled(Card)``;
export const StyledTopic = styled(Typography.Paragraph)`
font-weight: 600;
`;
export const StyledList = styled.ul`
padding-left: 18px;
`;
export const StyledListItem = styled.li`
font-style: italic;
padding-bottom: 0.5rem;
`;

View File

@@ -1,158 +0,0 @@
import { memo, useCallback, useEffect, useState } from 'react';
import { Button, Input } from 'antd';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ResizeTable } from 'components/ResizeTable';
import { useNotifications } from 'hooks/useNotifications';
import {
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
import { ExtendedChartDataset, GraphManagerProps } from './types';
import {
getDefaultTableDataSet,
saveLegendEntriesToLocalStorage,
} from './utils';
import './WidgetFullView.styles.scss';
function GraphManager({
data,
name,
yAxisUnit,
onToggleModelHandler,
setGraphsVisibilityStates,
graphsVisibilityStates = [], // not trimed
lineChartRef,
parentChartRef,
options,
}: GraphManagerProps): JSX.Element {
const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(
getDefaultTableDataSet(options, data),
);
useEffect(() => {
setTableDataSet(getDefaultTableDataSet(options, data));
}, [data, options]);
const { notifications } = useNotifications();
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked);
const checkBoxOnChangeHandler = useCallback(
(e: CheckboxChangeEvent, index: number): void => {
const newStates = [...graphsVisibilityStates];
newStates[index] = e.target.checked;
lineChartRef?.current?.toggleGraph(index, e.target.checked);
parentChartRef?.current?.toggleGraph(index, e.target.checked);
setGraphsVisibilityStates([...newStates]);
},
[
graphsVisibilityStates,
lineChartRef,
parentChartRef,
setGraphsVisibilityStates,
],
);
const labelClickedHandler = useCallback(
(labelIndex: number): void => {
const newGraphVisibilityStates = Array<boolean>(data.length).fill(false);
newGraphVisibilityStates[labelIndex] = true;
newGraphVisibilityStates.forEach((state, index) => {
lineChartRef?.current?.toggleGraph(index, state);
parentChartRef?.current?.toggleGraph(index, state);
});
setGraphsVisibilityStates(newGraphVisibilityStates);
},
[data.length, lineChartRef, parentChartRef, setGraphsVisibilityStates],
);
const columns = getGraphManagerTableColumns({
tableDataSet,
checkBoxOnChangeHandler,
graphVisibilityState: graphsVisibilityStates,
labelClickedHandler,
yAxisUnit,
isGraphDisabled: isDashboardLocked,
});
const filterHandler = useCallback(
(event: React.ChangeEvent<HTMLInputElement>): void => {
const value = event.target.value.toString().toLowerCase();
const updatedDataSet = tableDataSet.map((item) => {
if (item.label?.toLocaleLowerCase().includes(value)) {
return { ...item, show: true };
}
return { ...item, show: false };
});
setTableDataSet(updatedDataSet);
},
[tableDataSet],
);
const saveHandler = useCallback((): void => {
saveLegendEntriesToLocalStorage({
options,
graphVisibilityState: graphsVisibilityStates || [],
name,
});
notifications.success({
message: 'The updated graphs & legends are saved',
});
if (onToggleModelHandler) {
onToggleModelHandler();
}
}, [
graphsVisibilityStates,
name,
notifications,
onToggleModelHandler,
options,
]);
const dataSource = tableDataSet.filter(
(item, index) => index !== 0 && item.show,
);
return (
<div className="graph-manager-container">
<div className="graph-manager-header">
<Input onChange={filterHandler} placeholder="Filter Series" />
<div className="save-cancel-container">
<span className="save-cancel-button">
<Button type="default" onClick={onToggleModelHandler}>
Cancel
</Button>
</span>
<span className="save-cancel-button">
<Button type="primary" onClick={saveHandler}>
Save
</Button>
</span>
</div>
</div>
<div className="legends-list-container">
<ResizeTable
columns={columns}
dataSource={dataSource}
rowKey="index"
pagination={false}
style={{
maxHeight: 200,
overflowX: 'hidden',
overflowY: 'auto',
}}
/>
</div>
</div>
);
}
GraphManager.defaultProps = {
graphVisibilityStateHandler: undefined,
};
export default memo(GraphManager);

View File

@@ -1,18 +0,0 @@
import { TableColumnType as ColumnType } from 'antd';
import { DataSetProps } from '../types';
import Label from './Label';
export const getLabel = (
labelClickedHandler: (labelIndex: number) => void,
disabled?: boolean,
): ColumnType<DataSetProps> => ({
render: (label: string, record): JSX.Element => (
<Label
label={label}
labelIndex={record.index}
labelClickedHandler={labelClickedHandler}
disabled={disabled}
/>
),
});

View File

@@ -1,85 +0,0 @@
import { TableColumnType as ColumnType } from 'antd';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ColumnsKeyAndDataIndex, ColumnsTitle } from '../contants';
import { DataSetProps, ExtendedChartDataset } from '../types';
import { getGraphManagerTableHeaderTitle } from '../utils';
import CustomCheckBox from './CustomCheckBox';
import { getLabel } from './GetLabel';
export const getGraphManagerTableColumns = ({
tableDataSet,
checkBoxOnChangeHandler,
graphVisibilityState,
labelClickedHandler,
yAxisUnit,
isGraphDisabled,
}: GetGraphManagerTableColumnsProps): ColumnType<DataSetProps>[] => [
{
title: '',
width: 50,
dataIndex: ColumnsKeyAndDataIndex.Index,
key: ColumnsKeyAndDataIndex.Index,
render: (_: string, record: DataSetProps): JSX.Element => (
<CustomCheckBox
data={tableDataSet}
index={record.index}
checkBoxOnChangeHandler={checkBoxOnChangeHandler}
graphVisibilityState={graphVisibilityState}
disabled={isGraphDisabled}
/>
),
},
{
title: ColumnsTitle[ColumnsKeyAndDataIndex.Label],
width: 300,
dataIndex: ColumnsKeyAndDataIndex.Label,
key: ColumnsKeyAndDataIndex.Label,
...getLabel(labelClickedHandler, isGraphDisabled),
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Avg],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Avg,
key: ColumnsKeyAndDataIndex.Avg,
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Sum],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Sum,
key: ColumnsKeyAndDataIndex.Sum,
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Max],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Max,
key: ColumnsKeyAndDataIndex.Max,
},
{
title: getGraphManagerTableHeaderTitle(
ColumnsTitle[ColumnsKeyAndDataIndex.Min],
yAxisUnit,
),
width: 90,
dataIndex: ColumnsKeyAndDataIndex.Min,
key: ColumnsKeyAndDataIndex.Min,
},
];
interface GetGraphManagerTableColumnsProps {
tableDataSet: ExtendedChartDataset[];
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
labelClickedHandler: (labelIndex: number) => void;
graphVisibilityState: boolean[];
yAxisUnit?: string;
isGraphDisabled?: boolean;
}

View File

@@ -1,34 +0,0 @@
import { Tooltip } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { LabelContainer } from '../styles';
import { LabelProps } from '../types';
import { getAbbreviatedLabel } from '../utils';
function Label({
labelClickedHandler,
labelIndex,
label,
disabled = false,
}: LabelProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const onClickHandler = (): void => {
labelClickedHandler(labelIndex);
};
return (
<LabelContainer
isDarkMode={isDarkMode}
type="button"
disabled={disabled}
onClick={onClickHandler}
>
<Tooltip title={label} placement="topLeft">
{getAbbreviatedLabel(label)}
</Tooltip>
</LabelContainer>
);
}
export default Label;

View File

@@ -1,15 +0,0 @@
import { ChartData } from 'chart.js';
export const mockTestData: ChartData = {
labels: ['test1', 'test2'],
datasets: [
{
label: 'customer',
data: [481.60377358490564, 730.0000000000002],
},
{
label: 'demo-app',
data: [4471.4285714285725],
},
],
};

View File

@@ -1,12 +0,0 @@
import { LegendEntryProps } from '../FullView/types';
export const mocklegendEntryResult: LegendEntryProps[] = [
{
label: 'customer',
show: true,
},
{
label: 'demo-app',
show: false,
},
];

View File

@@ -1,3 +0,0 @@
import { GridValueComponentProps } from './types';
export const GridValueConfig: Pick<GridValueComponentProps, 'title'> = {};

View File

@@ -1,3 +0,0 @@
import Home from './Home';
export default Home;

View File

@@ -1,19 +0,0 @@
.loading-host-metrics {
padding: 24px 0;
height: 600px;
display: flex;
justify-content: center;
align-items: center;
.loading-host-metrics-content {
display: flex;
align-items: center;
flex-direction: column;
.loading-gif {
height: 72px;
margin-left: -24px;
}
}
}

View File

@@ -1,24 +0,0 @@
import { useTranslation } from 'react-i18next';
import { Typography } from 'antd';
import { DataSource } from 'types/common/queryBuilder';
import loadingPlaneUrl from '@/assets/Icons/loading-plane.gif';
import './HostMetricsLoading.styles.scss';
export function HostMetricsLoading(): JSX.Element {
const { t } = useTranslation('common');
return (
<div className="loading-host-metrics">
<div className="loading-host-metrics-content">
<img className="loading-gif" src={loadingPlaneUrl} alt="wait-icon" />
<Typography>
{t('pending_data_placeholder', {
dataSource: `host ${DataSource.METRICS}`,
})}
</Typography>
</div>
</div>
);
}

View File

@@ -1,8 +1,8 @@
import { Color } from '@signozhq/design-tokens';
import { Alert, Spin } from 'antd';
import { LoaderCircle, TriangleAlert } from 'lucide-react';
import { Callout } from '@signozhq/ui';
import { Spin } from 'antd';
import { LoaderCircle } from 'lucide-react';
import { ModalStateEnum } from '../types';
import { ModalStateEnum } from '../HeroSection/types';
function AlertMessage({
modalState,
@@ -12,14 +12,13 @@ function AlertMessage({
switch (modalState) {
case ModalStateEnum.WAITING:
return (
<Alert
message={
<Callout
title={
<div className="cloud-account-setup-form__alert-message">
<Spin
indicator={
<LoaderCircle
size={14}
color={Color.BG_AMBER_400}
className="anticon anticon-loading anticon-spin ant-spin-dot"
/>
}
@@ -28,21 +27,19 @@ function AlertMessage({
<span className="retry-time">10</span> secs...
</div>
}
className="cloud-account-setup-form__alert"
type="warning"
type="info"
showIcon={false}
/>
);
case ModalStateEnum.ERROR:
return (
<Alert
message={
<Callout
title={
<div className="cloud-account-setup-form__alert-message">
<TriangleAlert type="solid" size={15} color={Color.BG_SAKURA_400} />
{`We couldn't establish a connection to your AWS account. Please try again`}
</div>
}
type="error"
className="cloud-account-setup-form__alert"
/>
);
default:

View File

@@ -117,6 +117,12 @@
min-width: 140px !important;
}
&.azure {
.ant-select-selector {
min-width: 282px !important;
}
}
.ant-select-item-option-active {
background: var(--l3-background) !important;
}

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { Color } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui';
@@ -6,19 +6,29 @@ import { Select, Skeleton } from 'antd';
import { SelectProps } from 'antd/lib';
import logEvent from 'api/common/logEvent';
import { useListAccounts } from 'api/generated/services/cloudintegration';
import cx from 'classnames';
import { getAccountById } from 'container/Integrations/CloudIntegration/utils';
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
import {
CloudAccount as IntegrationCloudAccount,
IntegrationType,
} from 'container/Integrations/types';
import useUrlQuery from 'hooks/useUrlQuery';
import { ChevronDown, Dot, PencilLine, Plug, Plus } from 'lucide-react';
import { mapAccountDtoToAwsCloudAccount } from '../../mapAwsCloudAccountFromDto';
import { CloudAccount } from '../../types';
import AccountSettingsModal from './AccountSettingsModal';
import CloudAccountSetupModal from './CloudAccountSetupModal';
import AzureCloudAccountSetupModal from '../../AzureCloudServices/AddNewAccount/CloudAccountSetupModal';
import AzureAccountSettingsModal from '../../AzureCloudServices/EditAccount/AccountSettingsModal';
import {
mapAccountDtoToAwsCloudAccount,
mapAccountDtoToAzureCloudAccount,
} from '../../mapCloudAccountFromDto';
import AwsCloudAccountSetupModal from '../AddNewAccount/CloudAccountSetupModal';
import AwsAccountSettingsModal from '../EditAccount/AccountSettingsModal';
import { CloudAccount as AwsCloudAccount } from '../types';
import './AccountActions.style.scss';
function AccountActionsRenderer({
type,
accounts,
isLoading,
activeAccount,
@@ -27,9 +37,10 @@ function AccountActionsRenderer({
onIntegrationModalOpen,
onAccountSettingsModalOpen,
}: {
accounts: CloudAccount[] | undefined;
type: IntegrationType;
accounts: IntegrationCloudAccount[] | undefined;
isLoading: boolean;
activeAccount: CloudAccount | null;
activeAccount: IntegrationCloudAccount | null;
selectOptions: SelectProps['options'];
onAccountChange: (value: string) => void;
onIntegrationModalOpen: () => void;
@@ -57,9 +68,11 @@ function AccountActionsRenderer({
<Select
value={activeAccount?.providerAccountId}
options={selectOptions}
rootClassName="cloud-account-selector"
rootClassName={cx('cloud-account-selector', {
[type.toLowerCase()]: type,
})}
popupMatchSelectWidth={false}
placeholder="Select AWS Account"
placeholder={`Select ${type} Account`}
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
onChange={onAccountChange}
/>
@@ -102,21 +115,49 @@ function AccountActionsRenderer({
);
}
function AccountActions(): JSX.Element {
function AccountActions({ type }: { type: IntegrationType }): JSX.Element {
const urlQuery = useUrlQuery();
const navigate = useNavigate();
const { data: listAccountsResponse, isLoading } = useListAccounts({
cloudProvider: INTEGRATION_TYPES.AWS,
cloudProvider: type,
});
const accounts = useMemo((): CloudAccount[] | undefined => {
const accounts = useMemo((): IntegrationCloudAccount[] | undefined => {
const raw = listAccountsResponse?.data?.accounts;
if (!raw) {
return undefined;
}
return raw
.map(mapAccountDtoToAwsCloudAccount)
.filter((account): account is CloudAccount => account !== null);
}, [listAccountsResponse]);
const mappedAccounts: IntegrationCloudAccount[] = [];
if (type === IntegrationType.AWS_SERVICES) {
raw.forEach((account) => {
if (!account) {
return;
}
const mapped = mapAccountDtoToAwsCloudAccount(account);
if (mapped) {
mappedAccounts.push(mapped);
}
});
}
if (type === IntegrationType.AZURE_SERVICES) {
raw.forEach((account) => {
if (!account) {
return;
}
const mapped = mapAccountDtoToAzureCloudAccount(account);
if (mapped) {
mappedAccounts.push(mapped);
}
});
}
return mappedAccounts;
}, [listAccountsResponse, type]);
const initialAccount = useMemo(
() =>
@@ -127,9 +168,8 @@ function AccountActions(): JSX.Element {
[accounts, urlQuery],
);
const [activeAccount, setActiveAccount] = useState<CloudAccount | null>(
initialAccount,
);
const [activeAccount, setActiveAccount] =
useState<IntegrationCloudAccount | null>(initialAccount);
// Update state when initial value changes
useEffect(() => {
@@ -149,16 +189,17 @@ function AccountActions(): JSX.Element {
}, [initialAccount]);
const [isIntegrationModalOpen, setIsIntegrationModalOpen] = useState(false);
const startAccountConnectionAttempt = (): void => {
setIsIntegrationModalOpen(true);
logEvent('AWS Integration: Account connection attempt started', {});
logEvent(`${type} Integration: Account connection attempt started`, {});
};
const [isAccountSettingsModalOpen, setIsAccountSettingsModalOpen] =
useState(false);
const openAccountSettings = (): void => {
setIsAccountSettingsModalOpen(true);
logEvent('AWS Integration: Account settings viewed', {
logEvent(`${type} Integration: Account settings viewed`, {
cloudAccountId: activeAccount?.cloud_account_id,
});
};
@@ -166,13 +207,16 @@ function AccountActions(): JSX.Element {
// log telemetry event when an account is viewed.
useEffect(() => {
if (activeAccount) {
logEvent('AWS Integration: Account viewed', {
logEvent(`${type} Integration: Account viewed`, {
cloudAccountId: activeAccount?.cloud_account_id,
status: activeAccount?.status,
enabledRegions: activeAccount?.config?.regions,
enabledRegions:
'regions' in activeAccount.config
? activeAccount.config.regions
: activeAccount.config.resource_groups,
});
}
}, [activeAccount]);
}, [activeAccount, type]);
const selectOptions: SelectProps['options'] = useMemo(
() =>
@@ -188,6 +232,7 @@ function AccountActions(): JSX.Element {
return (
<div className="hero-section__actions">
<AccountActionsRenderer
type={type}
accounts={accounts}
isLoading={isLoading}
activeAccount={activeAccount}
@@ -204,17 +249,39 @@ function AccountActions(): JSX.Element {
/>
{isIntegrationModalOpen && (
<CloudAccountSetupModal
onClose={(): void => setIsIntegrationModalOpen(false)}
/>
<>
{type === IntegrationType.AWS_SERVICES && (
<AwsCloudAccountSetupModal
onClose={(): void => setIsIntegrationModalOpen(false)}
/>
)}
{type === IntegrationType.AZURE_SERVICES && (
<AzureCloudAccountSetupModal
onClose={(): void => setIsIntegrationModalOpen(false)}
/>
)}
</>
)}
{isAccountSettingsModalOpen && activeAccount && (
<AccountSettingsModal
onClose={(): void => setIsAccountSettingsModalOpen(false)}
account={activeAccount}
setActiveAccount={setActiveAccount}
/>
<>
{type === IntegrationType.AWS_SERVICES && (
<AwsAccountSettingsModal
onClose={(): void => setIsAccountSettingsModalOpen(false)}
account={activeAccount as AwsCloudAccount}
setActiveAccount={
setActiveAccount as Dispatch<SetStateAction<AwsCloudAccount | null>>
}
/>
)}
{type === IntegrationType.AZURE_SERVICES && (
<AzureAccountSettingsModal
onClose={(): void => setIsAccountSettingsModalOpen(false)}
account={activeAccount}
setActiveAccount={setActiveAccount}
/>
)}
</>
)}
</div>
);

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