Compare commits

..

20 Commits

Author SHA1 Message Date
Abhi Kumar
e3e27def66 Merge branch 'main' of https://github.com/SigNoz/signoz into chore/button-migration 2026-05-21 00:29:29 +05:30
Abhi Kumar
4fde760d8a chore: minor fix in sidebar 2026-05-21 00:27:18 +05:30
Abhi Kumar
9fded6d76b chore: removed majority references of periscope-btn 2026-05-21 00:21:24 +05:30
primus-bot[bot]
fb3e316ce9 chore(release): bump to v0.125.1 (#11381)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-05-20 17:46:41 +00:00
Manika Malhotra
b753b95a8a chore: replace antd badge with signozhq badge (#11377)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* chore: replace antd badge with signozhq badge

* chore: add badge to banned components
2026-05-20 14:24:52 +00:00
Vinicius Lourenço
4757550189 fix(alerts): ensure edit alert name is updated correctly and not override after save (#11348) 2026-05-20 13:57:52 +00:00
Vinicius Lourenço
96ad37fea9 fix(tanstack-table): reset page to 1 when change page size (#11344)
* fix(tanstack-table): reset page to 1 when change page size

* fix(tanstack): ensure page change callback is called
2026-05-20 13:57:40 +00:00
Vinicius Lourenço
5419e8461c fix(alerts-new): show tabs and breadcrumbs on create alert (#11316)
* fix(alerts-new): show tabs and breadcrumbs on create alert

* fix(pr): address comments

* fix(composite-query): not automatically showing the create alerts when have this query param

* fix(breadcrumb): align ui with periscope
2026-05-20 13:29:23 +00:00
Aditya Singh
e634eb4452 fix: expand waterfall ancestors on flamegraph click (#11373)
* fix: scroll to span in frontend mode when nodes are collapsed

* fix: fix tests

* feat: change default span details position
2026-05-20 13:28:22 +00:00
Piyush Singariya
a50bc53f4c chore: Accept body as Map in FE (#11291)
* fix: backend changes for message key postprocessing

* fix: message postprocessing

* chore: update in e2e tests

* fix: table view

* fix: support body as json in FE

* chore: separate frontend from backend changes

* chore: remove dead code
2026-05-20 12:52:53 +00:00
Srikanth Chekuri
9f60bdf54a chore: create source field in dashboards (#11367)
* chore: create source field in dashboards

* chore: consolidate checks to module

* chore: run generate

* chore: address review comments

* chore: separate test file

* chore: address review comments
2026-05-20 12:37:25 +00:00
Nikhil Mantri
e41639dea0 chore: function refactor (#11371) 2026-05-20 12:10:42 +00:00
Abhi Kumar
1f184a0bc0 chore: removed unused file 2026-05-20 17:40:09 +05:30
Abhi Kumar
9fc417310e Merge branch 'main' of https://github.com/SigNoz/signoz into chore/button-migration 2026-05-20 17:39:25 +05:30
Abhi Kumar
4d2263b3aa chore: updated text->ghost changes 2026-05-20 17:30:46 +05:30
Piyush Singariya
847bc71f4e fix: postprocess json logs message key (#11189)
* fix: backend changes for message key postprocessing

* fix: message postprocessing

* chore: update in e2e tests

* fix: table view

* chore: separate frontend from backend changes

* fix: integration tests
2026-05-20 11:46:27 +00:00
Ashwin Bhatkal
8d7d3e5c64 fix(metrics-explorer): show actual timestamp in metric sidesheet 'Last Received' tooltip (#11370) 2026-05-20 10:51:27 +00:00
primus-bot[bot]
74c875ec79 chore(release): bump to v0.125.0 (#11369)
Some checks failed
Release Drafter / update_release_draft (push) Has been cancelled
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
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-05-20 07:18:40 +00:00
Jatinderjit Singh
a27b7d3d8e move planned maintenance to alertmanager pipeline (#11130)
* 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>

* 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.

* 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>

* 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>

* 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>

* refactor: pass MaintenanceMuter directly to pipelineBuilder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: remove dead orgID param from task constructors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* rename buildReceiverStage -> createReceiverStage

* refactor: replace maintenanceMuteStage with notify.NewMuteStage

MaintenanceMuter already satisfies types.Muter, and pipelineBuilder has
its own pb.metrics, so the hand-rolled maintenanceMuteStage wrapper is
redundant. Use notify.NewMuteStage(pb.muter, pb.metrics) directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: hoist MuteStage construction out of the receiver loop

MuteStage holds no per-receiver state, so one instance shared across
all receivers is sufficient — matching how is/ss are handled upstream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: always initialize maintenanceStore; remove nil guards

Tests now use a real sqlrulestore-backed MaintenanceMuter instead of
passing nil. With nil no longer a valid input, remove the nil guards
in server.go and pipeline_builder.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: move MaintenanceMuter to Server and pass it to pipelineBuilder.New

- Remove muter from pipelineBuilder struct and newPipelineBuilder();
  pass it as a parameter to New() instead, consistent with inhibitor/silencer
- Store muter on Server so GetAlerts can call Mutes() alongside the
  inhibitor and silencer, ensuring maintenance-suppressed alerts show
  the correct muted status in API responses

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* remove redundant MemMarker wrapper

* feat: surface maintenance-suppressed alerts via mutedBy in GetAlerts

Alerts suppressed by an active maintenance window were being correctly
muted in the notification pipeline but appeared as state=active in the
v2 GetAlerts response, since MaintenanceMuter.Mutes had no marker
side-effect (unlike inhibitor/silencer).

Add MaintenanceMuter.MutedBy returning the matching window IDs, and
plumb a mutedByFunc callback through NewGettableAlertsFromAlertProvider
into AlertToOpenAPIAlert. The upstream v2 API forces state=suppressed
when mutedBy is non-empty, so the frontend's existing state-based
rendering picks it up without further changes.

Use the dedicated mutedBy field rather than SilencedBy to avoid
violating the "complete set of silence IDs" contract that anything
querying silences by ID would rely on.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* code cleanup

* refactor: move maintenance (planned downtime) to alertmanager packages

Types move from pkg/types/ruletypes/ to pkg/types/alertmanagertypes/:
- maintenance.go, recurrence.go, schedule.go (+ tests)

Store impl moves from pkg/ruler/rulestore/sqlrulestore/ to
pkg/alertmanager/alertmanagerstore/sqlalertmanagerstore/.

Maintenance windows mute alerts, so they belong with alertmanager
rather than the rule types.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test: add unit tests for MaintenanceMuter

Covers Mutes/MutedBy semantics (empty label, rule match, empty-RuleIDs
matches-all, future windows, multi-window) and the result cache
(single-fetch within TTL, stale-cache fallback on store error,
re-fetch after expiry, concurrency safety).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Update schema changes

* Re-add marker

* fix NewMaintenanceStore in tests

* Go lint fixes

* test: use mockery-generated mock for MaintenanceStore in muter tests

Replace hand-written fakeMaintenanceStore with a mockery-generated
MockMaintenanceStore, consistent with the alertmanagertest pattern.
Also adds MaintenanceStore to .mockery.yml so the mock stays in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: regenerate mocks via make gen-mocks

Picks up new MockHandler for the Handler interface in pkg/alertmanager
and regenerates MockMaintenanceStore with canonical mockery formatting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* cleanup test

* test: add e2e muting tests for maintenance window behaviour

* fix updates: omit empty endTime from serialization

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 04:20:16 +00:00
Abhi Kumar
8e01b0b293 chore: antd button -> signozui button migration 2026-05-18 21:09:02 +05:30
396 changed files with 5648 additions and 3525 deletions

View File

@@ -8,6 +8,14 @@ packages:
filename: "alertmanager.go"
structname: 'Mock{{.InterfaceName}}'
pkgname: '{{.SrcPackageName}}test'
github.com/SigNoz/signoz/pkg/types/alertmanagertypes:
interfaces:
MaintenanceStore:
config:
dir: '{{.InterfaceDir}}/alertmanagertypestest'
filename: "maintenance.go"
structname: 'Mock{{.InterfaceName}}'
pkgname: '{{.SrcPackageName}}test'
github.com/SigNoz/signoz/pkg/tokenizer:
config:
all: true

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.124.0
image: signoz/signoz:v0.125.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.124.0
image: signoz/signoz:v0.125.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.124.0}
image: signoz/signoz:${VERSION:-v0.125.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.124.0}
image: signoz/signoz:${VERSION:-v0.125.1}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -96,6 +96,53 @@ components:
- createdAt
- updatedAt
type: object
AlertmanagertypesMaintenanceKind:
enum:
- fixed
- recurring
type: string
AlertmanagertypesMaintenanceStatus:
enum:
- active
- upcoming
- expired
type: string
AlertmanagertypesPlannedMaintenance:
properties:
alertIds:
items:
type: string
nullable: true
type: array
createdAt:
format: date-time
type: string
createdBy:
type: string
description:
type: string
id:
type: string
kind:
$ref: '#/components/schemas/AlertmanagertypesMaintenanceKind'
name:
type: string
schedule:
$ref: '#/components/schemas/AlertmanagertypesSchedule'
status:
$ref: '#/components/schemas/AlertmanagertypesMaintenanceStatus'
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- name
- schedule
- status
- kind
type: object
AlertmanagertypesPostableChannel:
oneOf:
- required:
@@ -212,6 +259,23 @@ components:
required:
- name
type: object
AlertmanagertypesPostablePlannedMaintenance:
properties:
alertIds:
items:
type: string
nullable: true
type: array
description:
type: string
name:
type: string
schedule:
$ref: '#/components/schemas/AlertmanagertypesSchedule'
required:
- name
- schedule
type: object
AlertmanagertypesPostableRoutePolicy:
properties:
channels:
@@ -237,6 +301,60 @@ components:
- channels
- name
type: object
AlertmanagertypesRecurrence:
properties:
duration:
type: string
endTime:
format: date-time
nullable: true
type: string
repeatOn:
items:
$ref: '#/components/schemas/AlertmanagertypesRepeatOn'
nullable: true
type: array
repeatType:
$ref: '#/components/schemas/AlertmanagertypesRepeatType'
startTime:
format: date-time
type: string
required:
- startTime
- duration
- repeatType
type: object
AlertmanagertypesRepeatOn:
enum:
- sunday
- monday
- tuesday
- wednesday
- thursday
- friday
- saturday
type: string
AlertmanagertypesRepeatType:
enum:
- daily
- weekly
- monthly
type: string
AlertmanagertypesSchedule:
properties:
endTime:
format: date-time
type: string
recurrence:
$ref: '#/components/schemas/AlertmanagertypesRecurrence'
startTime:
format: date-time
type: string
timezone:
type: string
required:
- timezone
type: object
AuthtypesAttributeMapping:
properties:
email:
@@ -2224,6 +2342,8 @@ components:
type: boolean
org_id:
type: string
source:
$ref: '#/components/schemas/DashboardtypesSource'
updatedAt:
format: date-time
type: string
@@ -2253,6 +2373,12 @@ components:
timeRangeEnabled:
type: boolean
type: object
DashboardtypesSource:
enum:
- user
- system
- integration
type: object
DashboardtypesStorableDashboardData:
additionalProperties: {}
type: object
@@ -5137,17 +5263,6 @@ components:
message:
type: string
type: object
RuletypesMaintenanceKind:
enum:
- fixed
- recurring
type: string
RuletypesMaintenanceStatus:
enum:
- active
- upcoming
- expired
type: string
RuletypesMatchType:
enum:
- at_least_once
@@ -5175,59 +5290,6 @@ components:
- table
- graph
type: string
RuletypesPlannedMaintenance:
properties:
alertIds:
items:
type: string
nullable: true
type: array
createdAt:
format: date-time
type: string
createdBy:
type: string
description:
type: string
id:
type: string
kind:
$ref: '#/components/schemas/RuletypesMaintenanceKind'
name:
type: string
schedule:
$ref: '#/components/schemas/RuletypesSchedule'
status:
$ref: '#/components/schemas/RuletypesMaintenanceStatus'
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- name
- schedule
- status
- kind
type: object
RuletypesPostablePlannedMaintenance:
properties:
alertIds:
items:
type: string
nullable: true
type: array
description:
type: string
name:
type: string
schedule:
$ref: '#/components/schemas/RuletypesSchedule'
required:
- name
- schedule
type: object
RuletypesPostableRule:
properties:
alert:
@@ -5280,29 +5342,6 @@ components:
- clickhouse_sql
- promql
type: string
RuletypesRecurrence:
properties:
duration:
type: string
endTime:
format: date-time
nullable: true
type: string
repeatOn:
items:
$ref: '#/components/schemas/RuletypesRepeatOn'
nullable: true
type: array
repeatType:
$ref: '#/components/schemas/RuletypesRepeatType'
startTime:
format: date-time
type: string
required:
- startTime
- duration
- repeatType
type: object
RuletypesRenotify:
properties:
alertStates:
@@ -5314,22 +5353,6 @@ components:
interval:
type: string
type: object
RuletypesRepeatOn:
enum:
- sunday
- monday
- tuesday
- wednesday
- thursday
- friday
- saturday
type: string
RuletypesRepeatType:
enum:
- daily
- weekly
- monthly
type: string
RuletypesRollingWindow:
properties:
evalWindow:
@@ -5449,21 +5472,6 @@ components:
- promql_rule
- anomaly_rule
type: string
RuletypesSchedule:
properties:
endTime:
format: date-time
type: string
recurrence:
$ref: '#/components/schemas/RuletypesRecurrence'
startTime:
format: date-time
type: string
timezone:
type: string
required:
- timezone
type: object
RuletypesScheduleType:
enum:
- hourly
@@ -8024,7 +8032,7 @@ paths:
properties:
data:
items:
$ref: '#/components/schemas/RuletypesPlannedMaintenance'
$ref: '#/components/schemas/AlertmanagertypesPlannedMaintenance'
type: array
status:
type: string
@@ -8067,7 +8075,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/RuletypesPostablePlannedMaintenance'
$ref: '#/components/schemas/AlertmanagertypesPostablePlannedMaintenance'
responses:
"201":
content:
@@ -8075,7 +8083,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/RuletypesPlannedMaintenance'
$ref: '#/components/schemas/AlertmanagertypesPlannedMaintenance'
status:
type: string
required:
@@ -8178,7 +8186,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/RuletypesPlannedMaintenance'
$ref: '#/components/schemas/AlertmanagertypesPlannedMaintenance'
status:
type: string
required:
@@ -8232,7 +8240,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/RuletypesPostablePlannedMaintenance'
$ref: '#/components/schemas/AlertmanagertypesPostablePlannedMaintenance'
responses:
"204":
description: No Content

View File

@@ -49,6 +49,14 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
dashboard, err := module.Get(ctx, orgID, publicDashboard.DashboardID)
if err != nil {
return err
}
if err := dashboard.ErrIfNotPublishable(); err != nil {
return err
}
storablePublicDashboard, err := module.store.GetPublic(ctx, publicDashboard.DashboardID.StringValue())
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return err
@@ -129,6 +137,14 @@ func (module *module) UpdatePublic(ctx context.Context, orgID valuer.UUID, publi
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
dashboard, err := module.Get(ctx, orgID, publicDashboard.DashboardID)
if err != nil {
return err
}
if err := dashboard.ErrIfNotPublishable(); err != nil {
return err
}
return module.store.UpdatePublic(ctx, dashboardtypes.NewStorablePublicDashboardFromPublicDashboard(publicDashboard))
}
@@ -138,6 +154,10 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
return err
}
if err := dashboard.ErrIfNotDeletable(); err != nil {
return err
}
if dashboard.Locked {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
}
@@ -168,6 +188,14 @@ func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashb
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
dashboard, err := module.Get(ctx, orgID, dashboardID)
if err != nil {
return err
}
if err := dashboard.ErrIfNotPublishable(); err != nil {
return err
}
err = module.store.DeletePublic(ctx, dashboardID.StringValue())
if err != nil {
return err

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

@@ -49,7 +49,7 @@
"@signozhq/design-tokens": "2.1.4",
"@signozhq/icons": "0.4.0",
"@signozhq/resizable": "0.0.2",
"@signozhq/ui": "0.0.19",
"@signozhq/ui": "0.0.20",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",
@@ -241,4 +241,4 @@
"tmp": "0.2.4",
"vite": "npm:rolldown-vite@7.3.1"
}
}
}

View File

@@ -15,6 +15,7 @@
const BANNED_COMPONENTS = {
Typography: 'Use @signozhq/ui Typography instead of antd Typography.',
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
};
export default {

View File

@@ -77,8 +77,8 @@ importers:
specifier: 0.0.2
version: 0.0.2(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@signozhq/ui':
specifier: 0.0.19
version: 0.0.19(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
specifier: 0.0.20
version: 0.0.20(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
'@tanstack/react-table':
specifier: 8.21.3
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -3269,8 +3269,8 @@ packages:
peerDependencies:
react: ^18.2.0
'@signozhq/ui@0.0.19':
resolution: {integrity: sha512-2q6aRxN/PR4PlR2xJZAREEuvLPiDFggfFKzCW2Z5vHVVbrgnvZHWD1jPUuwszfEg0ceH3UvkwqceO7wN4uRJAA==}
'@signozhq/ui@0.0.20':
resolution: {integrity: sha512-rVC8OMfM1AsjsrWJWNzU8KlSw3hbWP5OF/jxuX3zQ5Fljo9PhtLksUF3jISfwLqbmWOBCopByr9UlKH46iAfYA==}
peerDependencies:
'@signozhq/icons': 0.3.0
react: ^18.2.0
@@ -3851,27 +3851,6 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
'@webassemblyjs/floating-point-hex-parser@1.13.2':
resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==}
'@webassemblyjs/helper-api-error@1.13.2':
resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==}
'@webassemblyjs/helper-buffer@1.14.1':
resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==}
'@webassemblyjs/helper-numbers@1.13.2':
resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==}
'@webassemblyjs/helper-wasm-bytecode@1.13.2':
resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==}
'@webassemblyjs/helper-wasm-section@1.14.1':
resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==}
'@xmldom/xmldom@0.8.13':
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
engines: {node: '>=10.0.0'}
@@ -12034,7 +12013,7 @@ snapshots:
- react-dom
- tailwindcss
'@signozhq/ui@0.0.19(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
'@signozhq/ui@0.0.20(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
dependencies:
'@chenglou/pretext': 0.0.5
'@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)

View File

@@ -47,7 +47,6 @@ export const TracesFunnels = Loadable(
import(/* webpackChunkName: "Traces Funnels" */ 'pages/TracesModulePage'),
);
export const TracesFunnelDetails = Loadable(
// eslint-disable-next-line sonarjs/no-identical-functions
() =>
import(
/* webpackChunkName: "Traces Funnel Details" */ 'pages/TracesModulePage'
@@ -313,13 +312,6 @@ export const PublicDashboardPage = Loadable(
),
);
export const AlertTypeSelectionPage = Loadable(
() =>
import(
/* webpackChunkName: "Alert Type Selection Page" */ 'pages/AlertTypeSelection'
),
);
export const MeterExplorerPage = Loadable(
() =>
import(/* webpackChunkName: "Meter Explorer Page" */ 'pages/MeterExplorer'),

View File

@@ -5,7 +5,6 @@ import {
AIAssistantPage,
AlertHistory,
AlertOverview,
AlertTypeSelectionPage,
AllAlertChannels,
AllErrors,
ApiMonitoring,
@@ -213,13 +212,6 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'LIST_ALL_ALERT',
},
{
path: ROUTES.ALERT_TYPE_SELECTION,
exact: true,
component: AlertTypeSelectionPage,
isPrivate: true,
key: 'ALERT_TYPE_SELECTION',
},
{
path: ROUTES.ALERTS_NEW,
exact: true,
@@ -533,18 +525,6 @@ export const LIST_LICENSES: AppRoutes = {
key: 'LIST_LICENSES',
};
export const oldRoutes = [
'/pipelines',
'/logs-explorer',
'/logs-explorer/live',
'/logs-save-views',
'/traces-save-views',
'/settings/access-tokens',
'/settings/api-keys',
'/messaging-queues',
'/alerts/edit',
];
export const oldNewRoutesMapping: Record<string, string> = {
'/pipelines': '/logs/pipelines',
'/logs-explorer': '/logs/logs-explorer',
@@ -555,7 +535,9 @@ export const oldNewRoutesMapping: Record<string, string> = {
'/settings/api-keys': '/settings/service-accounts',
'/messaging-queues': '/messaging-queues/overview',
'/alerts/edit': '/alerts/overview',
'/alerts/type-selection': '/alerts/new',
};
export const oldRoutes = Object.keys(oldNewRoutesMapping);
export const ROUTES_NOT_TO_BE_OVERRIDEN: string[] = [
ROUTES.WORKSPACE_LOCKED,

View File

@@ -18,6 +18,7 @@ import type {
} from 'react-query';
import type {
AlertmanagertypesPostablePlannedMaintenanceDTO,
CreateDowntimeSchedule201,
DeleteDowntimeScheduleByIDPathParameters,
GetDowntimeScheduleByID200,
@@ -25,7 +26,6 @@ import type {
ListDowntimeSchedules200,
ListDowntimeSchedulesParams,
RenderErrorResponseDTO,
RuletypesPostablePlannedMaintenanceDTO,
UpdateDowntimeScheduleByIDPathParameters,
} from '../sigNoz.schemas';
@@ -135,14 +135,14 @@ export const invalidateListDowntimeSchedules = async (
* @summary Create downtime schedule
*/
export const createDowntimeSchedule = (
ruletypesPostablePlannedMaintenanceDTO?: BodyType<RuletypesPostablePlannedMaintenanceDTO>,
alertmanagertypesPostablePlannedMaintenanceDTO?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateDowntimeSchedule201>({
url: `/api/v1/downtime_schedules`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostablePlannedMaintenanceDTO,
data: alertmanagertypesPostablePlannedMaintenanceDTO,
signal,
});
};
@@ -154,13 +154,13 @@ export const getCreateDowntimeScheduleMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data?: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
{ data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data?: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
{ data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO> },
TContext
> => {
const mutationKey = ['createDowntimeSchedule'];
@@ -174,7 +174,7 @@ export const getCreateDowntimeScheduleMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
{ data?: BodyType<RuletypesPostablePlannedMaintenanceDTO> }
{ data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO> }
> = (props) => {
const { data } = props ?? {};
@@ -188,7 +188,7 @@ export type CreateDowntimeScheduleMutationResult = NonNullable<
Awaited<ReturnType<typeof createDowntimeSchedule>>
>;
export type CreateDowntimeScheduleMutationBody =
| BodyType<RuletypesPostablePlannedMaintenanceDTO>
| BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>
| undefined;
export type CreateDowntimeScheduleMutationError =
ErrorType<RenderErrorResponseDTO>;
@@ -203,13 +203,13 @@ export const useCreateDowntimeSchedule = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data?: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
{ data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data?: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
{ data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO> },
TContext
> => {
return useMutation(getCreateDowntimeScheduleMutationOptions(options));
@@ -403,14 +403,14 @@ export const invalidateGetDowntimeScheduleByID = async (
*/
export const updateDowntimeScheduleByID = (
{ id }: UpdateDowntimeScheduleByIDPathParameters,
ruletypesPostablePlannedMaintenanceDTO?: BodyType<RuletypesPostablePlannedMaintenanceDTO>,
alertmanagertypesPostablePlannedMaintenanceDTO?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/downtime_schedules/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostablePlannedMaintenanceDTO,
data: alertmanagertypesPostablePlannedMaintenanceDTO,
signal,
});
};
@@ -424,7 +424,7 @@ export const getUpdateDowntimeScheduleByIDMutationOptions = <
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data?: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>;
},
TContext
>;
@@ -433,7 +433,7 @@ export const getUpdateDowntimeScheduleByIDMutationOptions = <
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data?: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>;
},
TContext
> => {
@@ -450,7 +450,7 @@ export const getUpdateDowntimeScheduleByIDMutationOptions = <
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data?: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -465,7 +465,7 @@ export type UpdateDowntimeScheduleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>
>;
export type UpdateDowntimeScheduleByIDMutationBody =
| BodyType<RuletypesPostablePlannedMaintenanceDTO>
| BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>
| undefined;
export type UpdateDowntimeScheduleByIDMutationError =
ErrorType<RenderErrorResponseDTO>;
@@ -482,7 +482,7 @@ export const useUpdateDowntimeScheduleByID = <
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data?: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>;
},
TContext
>;
@@ -491,7 +491,7 @@ export const useUpdateDowntimeScheduleByID = <
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data?: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
data?: BodyType<AlertmanagertypesPostablePlannedMaintenanceDTO>;
},
TContext
> => {

View File

@@ -134,6 +134,109 @@ export interface AlertmanagertypesGettableRoutePolicyDTO {
updatedBy?: string | null;
}
export enum AlertmanagertypesMaintenanceKindDTO {
fixed = 'fixed',
recurring = 'recurring',
}
export enum AlertmanagertypesMaintenanceStatusDTO {
active = 'active',
upcoming = 'upcoming',
expired = 'expired',
}
export enum AlertmanagertypesRepeatOnDTO {
sunday = 'sunday',
monday = 'monday',
tuesday = 'tuesday',
wednesday = 'wednesday',
thursday = 'thursday',
friday = 'friday',
saturday = 'saturday',
}
export enum AlertmanagertypesRepeatTypeDTO {
daily = 'daily',
weekly = 'weekly',
monthly = 'monthly',
}
export interface AlertmanagertypesRecurrenceDTO {
/**
* @type string
*/
duration: string;
/**
* @type string,null
* @format date-time
*/
endTime?: string | null;
/**
* @type array,null
*/
repeatOn?: AlertmanagertypesRepeatOnDTO[] | null;
repeatType: AlertmanagertypesRepeatTypeDTO;
/**
* @type string
* @format date-time
*/
startTime: string;
}
export interface AlertmanagertypesScheduleDTO {
/**
* @type string
* @format date-time
*/
endTime?: string;
recurrence?: AlertmanagertypesRecurrenceDTO;
/**
* @type string
* @format date-time
*/
startTime?: string;
/**
* @type string
*/
timezone: string;
}
export interface AlertmanagertypesPlannedMaintenanceDTO {
/**
* @type array,null
*/
alertIds?: string[] | null;
/**
* @type string
* @format date-time
*/
createdAt?: string;
/**
* @type string
*/
createdBy?: string;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
id: string;
kind: AlertmanagertypesMaintenanceKindDTO;
/**
* @type string
*/
name: string;
schedule: AlertmanagertypesScheduleDTO;
status: AlertmanagertypesMaintenanceStatusDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: string;
/**
* @type string
*/
updatedBy?: string;
}
export interface ConfigAuthorizationDTO {
/**
* @type string
@@ -1597,6 +1700,22 @@ export type AlertmanagertypesPostableChannelDTO = unknown & {
wechat_configs?: ConfigWechatConfigDTO[];
};
export interface AlertmanagertypesPostablePlannedMaintenanceDTO {
/**
* @type array,null
*/
alertIds?: string[] | null;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
name: string;
schedule: AlertmanagertypesScheduleDTO;
}
export interface AlertmanagertypesPostableRoutePolicyDTO {
/**
* @type array,null
@@ -2880,6 +2999,11 @@ export interface CoretypesPatchableObjectsDTO {
deletions: CoretypesObjectGroupDTO[] | null;
}
export enum DashboardtypesSourceDTO {
user = 'user',
system = 'system',
integration = 'integration',
}
export interface DashboardtypesDashboardDTO {
/**
* @type string
@@ -2903,6 +3027,7 @@ export interface DashboardtypesDashboardDTO {
* @type string
*/
org_id?: string;
source?: DashboardtypesSourceDTO;
/**
* @type string
* @format date-time
@@ -6116,15 +6241,6 @@ export interface RuletypesGettableTestRuleDTO {
message?: string;
}
export enum RuletypesMaintenanceKindDTO {
fixed = 'fixed',
recurring = 'recurring',
}
export enum RuletypesMaintenanceStatusDTO {
active = 'active',
upcoming = 'upcoming',
expired = 'expired',
}
export interface RuletypesRenotifyDTO {
/**
* @type array
@@ -6156,116 +6272,6 @@ export interface RuletypesNotificationSettingsDTO {
usePolicy?: boolean;
}
export enum RuletypesRepeatOnDTO {
sunday = 'sunday',
monday = 'monday',
tuesday = 'tuesday',
wednesday = 'wednesday',
thursday = 'thursday',
friday = 'friday',
saturday = 'saturday',
}
export enum RuletypesRepeatTypeDTO {
daily = 'daily',
weekly = 'weekly',
monthly = 'monthly',
}
export interface RuletypesRecurrenceDTO {
/**
* @type string
*/
duration: string;
/**
* @type string,null
* @format date-time
*/
endTime?: string | null;
/**
* @type array,null
*/
repeatOn?: RuletypesRepeatOnDTO[] | null;
repeatType: RuletypesRepeatTypeDTO;
/**
* @type string
* @format date-time
*/
startTime: string;
}
export interface RuletypesScheduleDTO {
/**
* @type string
* @format date-time
*/
endTime?: string;
recurrence?: RuletypesRecurrenceDTO;
/**
* @type string
* @format date-time
*/
startTime?: string;
/**
* @type string
*/
timezone: string;
}
export interface RuletypesPlannedMaintenanceDTO {
/**
* @type array,null
*/
alertIds?: string[] | null;
/**
* @type string
* @format date-time
*/
createdAt?: string;
/**
* @type string
*/
createdBy?: string;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
id: string;
kind: RuletypesMaintenanceKindDTO;
/**
* @type string
*/
name: string;
schedule: RuletypesScheduleDTO;
status: RuletypesMaintenanceStatusDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: string;
/**
* @type string
*/
updatedBy?: string;
}
export interface RuletypesPostablePlannedMaintenanceDTO {
/**
* @type array,null
*/
alertIds?: string[] | null;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
name: string;
schedule: RuletypesScheduleDTO;
}
export type RuletypesPostableRuleDTOAnnotations = { [key: string]: string };
export type RuletypesPostableRuleDTOLabels = { [key: string]: string };
@@ -7793,7 +7799,7 @@ export type ListDowntimeSchedules200 = {
/**
* @type array
*/
data: RuletypesPlannedMaintenanceDTO[];
data: AlertmanagertypesPlannedMaintenanceDTO[];
/**
* @type string
*/
@@ -7801,7 +7807,7 @@ export type ListDowntimeSchedules200 = {
};
export type CreateDowntimeSchedule201 = {
data: RuletypesPlannedMaintenanceDTO;
data: AlertmanagertypesPlannedMaintenanceDTO;
/**
* @type string
*/
@@ -7815,7 +7821,7 @@ export type GetDowntimeScheduleByIDPathParameters = {
id: string;
};
export type GetDowntimeScheduleByID200 = {
data: RuletypesPlannedMaintenanceDTO;
data: AlertmanagertypesPlannedMaintenanceDTO;
/**
* @type string
*/

View File

@@ -0,0 +1,17 @@
.breadcrumb {
padding-left: 16px;
ol {
align-items: center;
}
:global(.ant-breadcrumb-separator) {
color: var(--muted-foreground);
}
}
.divider {
border-color: var(--l1-border);
margin: 16px 0;
margin-top: 10px;
}

View File

@@ -0,0 +1,32 @@
import { Breadcrumb, Divider } from 'antd';
import styles from './AlertBreadcrumb.module.scss';
import BreadcrumbItem, { BreadcrumbItemConfig } from './BreadcrumbItem';
export interface AlertBreadcrumbProps {
items: BreadcrumbItemConfig[];
className?: string;
showDivider?: boolean;
}
function AlertBreadcrumb({
items,
className,
showDivider = true,
}: AlertBreadcrumbProps): JSX.Element {
const breadcrumbItems = items.map((item) => ({
title: <BreadcrumbItem {...item} />,
}));
return (
<>
<Breadcrumb
className={`${styles.breadcrumb} ${className || ''}`}
items={breadcrumbItems}
/>
{showDivider && <Divider className={styles.divider} />}
</>
);
}
export default AlertBreadcrumb;

View File

@@ -0,0 +1,9 @@
.item {
--button-padding: 0;
--button-font-size: var(--periscope-font-size-base);
}
.itemLast {
color: var(--muted-foreground);
font-size: var(--periscope-font-size-base);
}

View File

@@ -0,0 +1,45 @@
import { Button } from '@signozhq/ui/button';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { isModifierKeyPressed } from 'utils/app';
import styles from './BreadcrumbItem.module.scss';
export type BreadcrumbItemConfig =
| {
title: string | null;
route?: string;
}
| {
title: string | null;
isLast?: true;
};
function BreadcrumbItem({
title,
...props
}: BreadcrumbItemConfig): JSX.Element {
const { safeNavigate } = useSafeNavigate();
if ('isLast' in props) {
return <div className={styles.itemLast}>{title}</div>;
}
return (
<Button
variant="ghost"
color="secondary"
className={styles.item}
onClick={(e: React.MouseEvent): void => {
if (!('route' in props) || !props.route) {
return;
}
safeNavigate(props.route, { newTab: isModifierKeyPressed(e) });
}}
>
{title}
</Button>
);
}
export default BreadcrumbItem;

View File

@@ -0,0 +1,6 @@
export { default } from './AlertBreadcrumb';
export {
default as BreadcrumbItem,
type BreadcrumbItemConfig,
} from './BreadcrumbItem';
export type { AlertBreadcrumbProps } from './AlertBreadcrumb';

View File

@@ -5,7 +5,6 @@ import { useSelector } from 'react-redux';
import { Loader, Search } from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import {
Button,
Flex,
Input,
InputRef,
@@ -17,6 +16,7 @@ import {
Tooltip,
} from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import type { FilterDropdownProps } from 'antd/lib/table/interface';
import logEvent from 'api/common/logEvent';
import {
@@ -105,9 +105,8 @@ const getColumnSearchProps = (
/>
<Space>
<Button
type="primary"
size="small"
onClick={(): void => handleSearch(selectedKeys as string[], confirm)}
size="sm"
>
<Flex align="center" gap={4}>
<Search size="md" />
@@ -116,17 +115,19 @@ const getColumnSearchProps = (
</Button>
<Button
onClick={(): void => clearFilters && handleReset(clearFilters, confirm)}
size="small"
style={{ width: 90 }}
size="sm"
variant="outlined"
color="secondary"
>
Reset
</Button>
<Button
type="link"
size="small"
onClick={(): void => {
close();
}}
size="sm"
variant="link"
>
close
</Button>

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useMutation } from 'react-query';
import { Check, ChevronsDown, ScrollText, X } from '@signozhq/icons';
import { Button, Flex, Modal } from 'antd';
import { Flex, Modal } from 'antd';
import updateUserPreference from 'api/v1/user/preferences/name/update';
import cx from 'classnames';
import { USER_PREFERENCES } from 'constants/userPreferences';
@@ -14,6 +14,7 @@ import { UserPreference } from 'types/api/preferences/preference';
import ChangelogRenderer from './components/ChangelogRenderer';
import './ChangelogModal.styles.scss';
import { Button } from '@signozhq/ui/button';
interface Props {
changelog: ChangelogSchema;
@@ -115,13 +116,13 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
>
{!isCloudUser && (
<div className="changelog-modal-footer-ctas">
<Button type="default" onClick={onClose}>
<Button onClick={onClose} variant="outlined" color="secondary">
<Flex align="center" gap="4px">
<X size="md" />
Skip for now
</Flex>
</Button>
<Button type="primary" onClick={onClickUpdateWorkspace}>
<Button onClick={onClickUpdateWorkspace}>
<Flex align="center" gap="4px">
<Check size="md" />
Update my workspace

View File

@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Modal } from 'antd';
import { Modal } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
import { useNotifications } from 'hooks/useNotifications';
@@ -72,6 +73,8 @@ export default function ChatSupportGateway(): JSX.Element {
setIsAddCreditCardModalOpen(true);
}}
variant="outlined"
color="secondary"
>
<MessageSquareText size={24} />
</Button>
@@ -90,19 +93,19 @@ export default function ChatSupportGateway(): JSX.Element {
key="cancel"
onClick={(): void => setIsAddCreditCardModalOpen(false)}
className="cancel-btn"
icon={<X size={16} />}
variant="outlined"
color="secondary"
prefix={<X size={16} />}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
icon={<CreditCard size={16} />}
size="middle"
loading={isLoadingBilling}
disabled={isLoadingBilling}
onClick={handleAddCreditCard}
className="add-credit-card-btn"
prefix={<CreditCard size={16} />}
>
Add Credit Card
</Button>,

View File

@@ -137,7 +137,6 @@ function CreateServiceAccountModal(): JSX.Element {
<AuthZTooltip checks={[SACreatePermission]}>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form="create-sa-form"
variant="solid"
color="primary"

View File

@@ -1,5 +1,5 @@
import { Calendar } from '@signozhq/ui/calendar';
import { Button } from 'antd';
import { Button } from '@signozhq/ui/button';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
import { Calendar as CalendarIcon, Check, X } from '@signozhq/icons';
@@ -78,18 +78,20 @@ function CalendarContainer({
<div className="calendar-actions">
<Button
type="primary"
className="periscope-btn secondary cancel-btn"
className="cancel-btn"
onClick={onCancel}
icon={<X size={12} />}
prefix={<X size={12} />}
variant="outlined"
color="secondary"
>
Cancel
</Button>
<Button
type="primary"
className="periscope-btn primary apply-btn"
className="apply-btn"
onClick={onApply}
icon={<Check size={12} />}
prefix={<Check size={12} />}
variant="solid"
color="primary"
>
Apply
</Button>

View File

@@ -108,7 +108,7 @@
}
.info-text:hover {
&.ant-btn-text {
& {
background-color: unset !important;
}
}

View File

@@ -8,7 +8,6 @@ import {
} from 'react';
import { useLocation } from 'react-router-dom';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -32,6 +31,7 @@ import TimezonePicker from './TimezonePicker';
import { Timezone } from './timezoneUtils';
import './CustomTimePicker.styles.scss';
import { Button } from '@signozhq/ui/button';
const TO_MILLISECONDS_FACTOR = 1000_000;
@@ -177,13 +177,14 @@ function CustomTimePickerPopoverContent({
<div className="relative-date-time-section">
{options.map((option) => (
<Button
type="text"
className="time-btns"
key={option.label + option.value}
onClick={(): void => {
handleExitLiveLogs();
onSelectHandler(option.label, option.value);
}}
variant="ghost"
color="secondary"
>
{option.label}
</Button>
@@ -249,15 +250,15 @@ function CustomTimePickerPopoverContent({
{isLogsExplorerPage && isLogsListView && (
<Button
className={cx('data-time-live', isLiveLogsEnabled ? 'active' : '')}
type="text"
onClick={handleGoLive}
variant="ghost"
color="secondary"
>
Live
</Button>
)}
{options.map((option) => (
<Button
type="text"
key={option.label + option.value}
onClick={(e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation();
@@ -271,6 +272,8 @@ function CustomTimePickerPopoverContent({
? option.value === 'custom' && !isLiveLogsEnabled && 'active'
: selectedTime === option.value && !isLiveLogsEnabled && 'active',
)}
variant="ghost"
color="secondary"
>
<span className="time-label">{option.label}</span>
@@ -370,11 +373,12 @@ function CustomTimePickerPopoverContent({
<div className="timezone-container__right">
<Button
type="text"
size="small"
className="periscope-btn text timezone-change-button"
className="timezone-change-button"
onClick={handleTimezoneHintClick}
icon={<PenLine size={10} />}
size="sm"
variant="ghost"
prefix={<PenLine size={10} />}
color="none"
>
Change Timezone
</Button>

View File

@@ -106,7 +106,7 @@ describe.each([
renderWithStore(dataSource);
const button = screen.getByTestId(testId);
expect(button).toBeInTheDocument();
expect(button).toHaveClass('periscope-btn', 'ghost');
expect(button).not.toBeDisabled();
});
it('shows popover with export options when download button is clicked', () => {

View File

@@ -1,6 +1,7 @@
import { useCallback, useMemo, useState } from 'react';
import { Button, Popover, Radio, Tooltip } from 'antd';
import { Popover, Radio, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import { TelemetryFieldKey } from 'api/v5/v5';
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
import { Download, LoaderCircle } from '@signozhq/icons';
@@ -104,12 +105,11 @@ export default function DownloadOptionsMenu({
)}
<Button
type="primary"
icon={<Download size={16} />}
onClick={handleExport}
className="export-button"
disabled={isDownloading}
loading={isDownloading}
prefix={<Download size={16} />}
>
Export
</Button>
@@ -137,16 +137,18 @@ export default function DownloadOptionsMenu({
>
<Tooltip title="Download" placement="top">
<Button
className="periscope-btn ghost"
icon={
data-testid={`periscope-btn-download-${dataSource}`}
disabled={isDownloading}
variant="outlined"
color="secondary"
size="icon"
prefix={
isDownloading ? (
<LoaderCircle size={14} className="animate-spin" />
) : (
<Download size={14} />
)
}
data-testid={`periscope-btn-download-${dataSource}`}
disabled={isDownloading}
/>
</Tooltip>
</Popover>

View File

@@ -1,8 +1,9 @@
import { useState } from 'react';
import { Ellipsis } from '@signozhq/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { Dropdown, MenuProps } from 'antd';
import './DropDown.styles.scss';
import { Button } from '@signozhq/ui/button';
function DropDown({
element,
@@ -31,12 +32,12 @@ function DropDown({
open={isDdOpen}
>
<Button
type="link"
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
variant="link"
>
<Ellipsis className="dropdown-icon" size={16} />
</Button>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Button, Modal, Tag } from 'antd';
import { Modal, Tag } from 'antd';
import { CircleAlert, X } from '@signozhq/icons';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { useAppContext } from 'providers/App/App';
@@ -9,6 +9,7 @@ import APIError from 'types/api/error';
import ErrorContent from './components/ErrorContent';
import './ErrorModal.styles.scss';
import { Button } from '@signozhq/ui/button';
type Props = {
error: APIError;
@@ -73,10 +74,11 @@ function ErrorModal({
<div className="error-modal__version-placeholder" />
)}
<Button
type="default"
className="close-button"
onClick={handleClose}
data-testid="close-button"
variant="outlined"
color="secondary"
>
<X size={16} color={Color.BG_VANILLA_400} />
</Button>

View File

@@ -1,16 +1,8 @@
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import {
Button,
Col,
Dropdown,
MenuProps,
Popover,
Row,
Select,
Space,
} from 'antd';
import { Col, Dropdown, MenuProps, Popover, Row, Select, Space } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';
@@ -159,7 +151,6 @@ function ExplorerCard({
],
};
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
const saveButtonIcon = isQueryUpdated ? null : <Save size="md" />;
const showSaveView = false;
@@ -210,7 +201,7 @@ function ExplorerCard({
</Space>
)}
{isQueryUpdated && (
<Button type="primary" icon={<Save />} onClick={onUpdateQueryHandler}>
<Button onClick={onUpdateQueryHandler} prefix={<Save />}>
Save changes
</Button>
)}
@@ -230,9 +221,10 @@ function ExplorerCard({
onOpenChange={handleOpenChange}
>
<Button
type={saveButtonType}
icon={saveButtonIcon}
data-testid="traces-save-view-action"
variant="outlined"
color="secondary"
prefix={saveButtonIcon ?? undefined}
>
{isQueryUpdated
? SaveButtonText.SAVE_AS_NEW_VIEW

View File

@@ -54,7 +54,7 @@ function SortableField({
<span className={styles.fieldKey}>{field.name}</span>
</div>
<Button
className={cx(styles.removeBtn, 'periscope-btn')}
className={styles.removeBtn}
variant="outlined"
color="destructive"
size="sm"

View File

@@ -89,7 +89,7 @@ function OtherFields({
<span className={styles.fieldKey}>{attr.name}</span>
{!isAtLimit && (
<Button
className={cx(styles.addBtn, 'periscope-btn')}
className={styles.addBtn}
variant="outlined"
color="secondary"
size="sm"

View File

@@ -1,8 +1,9 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { Button, Input, Radio, RadioChangeEvent } from 'antd';
import { Input, Radio, RadioChangeEvent } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
@@ -125,11 +126,11 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
<div className="feedback-modal-content-footer">
<Button
className="periscope-btn primary"
type="primary"
onClick={handleSubmit}
loading={isLoading}
disabled={feedback.length === 0}
variant="solid"
color="primary"
>
Submit
</Button>

View File

@@ -141,6 +141,7 @@ function HeaderRightSection({
>
<Button
variant="ghost"
color="secondary"
size="icon"
className="share-feedback-btn"
aria-label="Feedback"
@@ -164,14 +165,15 @@ function HeaderRightSection({
>
<Button
variant="ghost"
color="secondary"
size="icon"
aria-label="Announcements"
prefix={<Inbox size={14} />}
onClick={(): void => {
logEvent('Announcements: Clicked', {
page: location.pathname,
});
}}
prefix={<Inbox size={14} />}
/>
</Popover>
)}
@@ -190,6 +192,7 @@ function HeaderRightSection({
>
<Button
variant="ghost"
color="secondary"
size="icon"
aria-label="Share"
prefix={<Globe size={14} />}

View File

@@ -4,8 +4,9 @@ import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
import { Button, Switch } from 'antd';
import { Switch } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
@@ -155,9 +156,10 @@ function ShareURLModal(): JSX.Element {
</div>
<Button
className="periscope-btn secondary"
onClick={handleCopyURL}
icon={isURLCopied ? <Check size={14} /> : <Link2 size={14} />}
variant="outlined"
color="secondary"
prefix={isURLCopied ? <Check size={14} /> : <Link2 size={14} />}
>
Copy page link
</Button>

View File

@@ -1,8 +1,8 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { useNotifications } from 'hooks/useNotifications';
import { CircleCheckBig, HandPlatter } from '@signozhq/icons';
@@ -57,17 +57,18 @@ export default function WaitlistFragment({
</Typography.Text>
<Button
className="periscope-btn join-waitlist-btn"
type="default"
className="join-waitlist-btn"
loading={isSubmitting}
icon={
onClick={handleJoinWaitlist}
variant="outlined"
color="secondary"
prefix={
isSuccess ? (
<CircleCheckBig size={16} color={Color.BG_FOREST_500} />
) : (
<HandPlatter size={16} />
)
}
onClick={handleJoinWaitlist}
>
Get early access
</Button>

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { Button, Input } from 'antd';
import { Input } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import cx from 'classnames';
import { X } from '@signozhq/icons';
@@ -55,9 +56,12 @@ function InputWithLabel({
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
{onClose && (
<Button
className="periscope-btn ghost close-btn"
icon={closeIcon || <X size={16} />}
className="close-btn"
onClick={onClose}
variant="outlined"
color="secondary"
size="icon"
prefix={(closeIcon as JSX.Element) || <X size={16} />}
/>
)}
</div>

View File

@@ -2,7 +2,7 @@
color: var(--bg-amber-500);
border-color: var(--bg-amber-500);
> .ant-btn:hover {
> button:hover {
color: var(--bg-amber-400) !important;
border-color: var(--bg-amber-300) !important;
}

View File

@@ -1,8 +1,9 @@
import { useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Modal, Tooltip } from 'antd';
import { Modal, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
import cx from 'classnames';
@@ -168,9 +169,11 @@ function LaunchChatSupport({
overlayClassName="tooltip-overlay"
>
<Button
className={cx('periscope-btn', 'facing-issue-button', className)}
className={cx('facing-issue-button', className)}
onClick={handleFacingIssuesClick}
icon={<CircleHelp size={14} />}
variant="outlined"
color="secondary"
prefix={<CircleHelp size={14} />}
>
{buttonText || 'Facing issues?'}
</Button>
@@ -189,19 +192,19 @@ function LaunchChatSupport({
key="cancel"
onClick={(): void => setIsAddCreditCardModalOpen(false)}
className="cancel-btn"
icon={<X size={16} />}
variant="outlined"
color="secondary"
prefix={<X size={16} />}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
icon={<CreditCard size={16} />}
size="middle"
loading={isLoadingBilling}
disabled={isLoadingBilling}
onClick={handleAddCreditCard}
className="add-credit-card-btn"
prefix={<CreditCard size={16} />}
>
Add Credit Card
</Button>,

View File

@@ -1,9 +1,9 @@
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { ArrowUpRight } from '@signozhq/icons';
import { openInNewTab } from 'utils/navigation';
import './LearnMore.styles.scss';
import { Button } from '@signozhq/ui/button';
type LearnMoreProps = {
text?: string;
@@ -19,7 +19,7 @@ function LearnMore({ text, url, onClick }: LearnMoreProps): JSX.Element {
}
};
return (
<Button type="link" className="learn-more" onClick={handleClick}>
<Button className="learn-more" onClick={handleClick} variant="link">
<div className="learn-more__text">{text}</div>
<ArrowUpRight size={16} color={Color.BG_ROBIN_400} />
</Button>

View File

@@ -17,7 +17,7 @@
.log-detail-drawer__title-right {
display: flex;
align-items: center;
.ant-btn {
button {
display: flex;
align-items: center;
}

View File

@@ -50,6 +50,7 @@ import {
import { JsonView } from 'periscope/components/JsonView';
import { useAppContext } from 'providers/App/App';
import { AppState } from 'store/reducers';
import { ILogBody } from 'types/api/logs/log';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -217,20 +218,17 @@ function LogDetailInner({
const logBody = useMemo(() => {
if (!isBodyJsonQueryEnabled) {
return log?.body || '';
return (log?.body as string) ?? '';
}
try {
const json = JSON.parse(log?.body || '');
if (typeof json?.message === 'string' && json.message !== '') {
return json.message;
}
return log?.body || '';
} catch {
return log?.body || '';
// Feature enabled: body is always a map; message is always a string
const bodyObj = log?.body as ILogBody;
if (!bodyObj) {
return '';
}
if (bodyObj.message) {
return bodyObj.message;
}
return JSON.stringify(bodyObj);
}, [isBodyJsonQueryEnabled, log?.body]);
const htmlBody = useMemo(

View File

@@ -166,7 +166,7 @@
border-left: 1px solid var(--l1-border) !important;
}
.ant-btn-default {
button {
border: none;
box-shadow: none;
}

View File

@@ -9,7 +9,7 @@
border: 1px solid var(--l1-border);
background: var(--l2-background);
.ant-btn-default {
button {
border: none;
box-shadow: none;
padding: 9px;

View File

@@ -1,8 +1,9 @@
import { memo, MouseEventHandler } from 'react';
import { Link, TextSelect } from '@signozhq/icons';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import './LogLinesActionButtons.styles.scss';
import { Button } from '@signozhq/ui/button';
export interface LogLinesActionButtonsProps {
handleShowContext: MouseEventHandler<HTMLElement>;
@@ -19,18 +20,22 @@ function LogLinesActionButtons({
<div className={`log-line-action-buttons ${customClassName}`}>
<Tooltip title="Show in Context">
<Button
size="small"
icon={<TextSelect size={14} />}
className="show-context-btn"
onClick={handleShowContext}
size="sm"
variant="outlined"
color="secondary"
prefix={<TextSelect size={14} />}
/>
</Tooltip>
<Tooltip title="Copy Link">
<Button
size="small"
icon={<Link size={14} />}
onClick={onLogCopy}
className="copy-log-btn"
size="sm"
variant="outlined"
color="secondary"
prefix={<Link size={14} />}
/>
</Tooltip>
</div>

View File

@@ -9,7 +9,10 @@ import { Color } from '@signozhq/design-tokens';
import { Tooltip } from 'antd';
import { VIEW_TYPES } from 'components/LogDetail/constants';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
import {
getBodyDisplayString,
getSanitizedLogBody,
} from 'container/LogDetailedView/utils';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
// hooks
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -99,7 +102,7 @@ function RawLogView({
// Check if body is selected
const showBody = selectedFields.some((field) => field.name === 'body');
if (showBody) {
parts.push(`${attributesText} ${data.body}`);
parts.push(`${attributesText} ${getBodyDisplayString(data.body)}`);
} else {
parts.push(attributesText);
}

View File

@@ -2,7 +2,10 @@ import type { ReactElement } from 'react';
import { useMemo } from 'react';
import TanStackTable from 'components/TanStackTableView';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
import {
getBodyDisplayString,
getSanitizedLogBody,
} from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types';
import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
@@ -87,7 +90,7 @@ export function useLogsTableColumns({
? {
id: 'body',
header: 'Body',
accessorFn: (log): string => log.body,
accessorFn: (log): string => getBodyDisplayString(log.body),
canBeHidden: false,
width: { default: '100%', min: 300 },
cell: ({ value, isActive }): ReactElement => (

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
import { Input, InputNumber, Popover, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import type { DefaultOptionType } from 'antd/es/select';
import cx from 'classnames';
import { LogViewMode } from 'container/LogsTable';
@@ -223,7 +224,8 @@ function OptionsMenu({
<Button
onClick={(): void => setIsFontSizeOptionsOpen(false)}
className="back-btn"
type="text"
variant="ghost"
color="secondary"
>
<ChevronLeft size={14} className="icon" />
<Typography.Text className="text">Select font size</Typography.Text>
@@ -235,7 +237,8 @@ function OptionsMenu({
setFontSizeValue(FontSize.SMALL);
}}
className="option-btn"
type="text"
variant="ghost"
color="secondary"
>
<Typography.Text className="text">{FontSize.SMALL}</Typography.Text>
{fontSizeValue === FontSize.SMALL && (
@@ -247,7 +250,8 @@ function OptionsMenu({
setFontSizeValue(FontSize.MEDIUM);
}}
className="option-btn"
type="text"
variant="ghost"
color="secondary"
>
<Typography.Text className="text">{FontSize.MEDIUM}</Typography.Text>
{fontSizeValue === FontSize.MEDIUM && (
@@ -259,7 +263,8 @@ function OptionsMenu({
setFontSizeValue(FontSize.LARGE);
}}
className="option-btn"
type="text"
variant="ghost"
color="secondary"
>
<Typography.Text className="text">{FontSize.LARGE}</Typography.Text>
{fontSizeValue === FontSize.LARGE && (
@@ -338,10 +343,11 @@ function OptionsMenu({
<div className="title">Font Size</div>
<Button
className="value"
type="text"
onClick={(): void => {
setIsFontSizeOptionsOpen(true);
}}
variant="ghost"
color="secondary"
>
<Typography.Text className="font-value">{fontSizeValue}</Typography.Text>
<ChevronRight size={14} className="icon" />
@@ -472,9 +478,11 @@ function LogsFormatOptionsMenu({
>
<Tooltip title="Options">
<Button
className="periscope-btn ghost"
icon={<SlidersVertical size="md" />}
data-testid="periscope-btn-format-options"
variant="outlined"
color="secondary"
size="icon"
prefix={<SlidersVertical size={14} />}
/>
</Tooltip>
</Popover>

View File

@@ -1,5 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { Button } from 'antd';
import cx from 'classnames';
import { useOnboardingStatus } from 'hooks/messagingQueue/useOnboardingStatus';
import { Bolt, FolderTree } from '@signozhq/icons';
@@ -8,6 +7,7 @@ import { MessagingQueueHealthCheckService } from 'pages/MessagingQueues/Messagin
import AttributeCheckList from './AttributeCheckList';
import './MessagingQueueHealthCheck.styles.scss';
import { Button } from '@signozhq/ui/button';
interface MessagingQueueHealthCheckProps {
serviceToInclude: string[];
@@ -94,7 +94,9 @@ function MessagingQueueHealthCheck({
'config-btn',
missingConfiguration ? 'missing-config-btn' : '',
)}
icon={<Bolt size={12} />}
variant="outlined"
color="secondary"
prefix={<Bolt size={12} />}
>
<div className="config-btn-content">
{missingConfiguration

View File

@@ -18,8 +18,9 @@ import {
RefreshCw,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Checkbox, Select } from 'antd';
import { Checkbox, Select } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import cx from 'classnames';
import TextToolTip from 'components/TextToolTip/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';
@@ -767,11 +768,11 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
<div className="option-badge">{capitalize(option.type)}</div>
)}
{option.value && ensureValidOption(option.value) && (
<Button type="text" className="only-btn">
<Button className="only-btn" variant="ghost" color="secondary">
{currentToggleTagValue({ option: option.value })}
</Button>
)}
<Button type="text" className="toggle-btn">
<Button className="toggle-btn" variant="ghost" color="secondary">
Toggle
</Button>
</div>

View File

@@ -66,7 +66,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
const handleChangeGroupByKeys = useCallback(
(value: IBuilderQuery['groupBy']) => {
handleChangeQueryData('groupBy', value, { runAfterUpdate: true });
handleChangeQueryData('groupBy', value);
},
[handleChangeQueryData],
);

View File

@@ -13,12 +13,12 @@ import { javascript } from '@codemirror/lang-javascript';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, { EditorView, keymap } from '@uiw/react-codemirror';
import { Button } from 'antd';
import { Having } from 'api/v5/v5';
import { useQueryBuilderV2Context } from 'components/QueryBuilderV2/QueryBuilderV2Context';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { ChevronUp } from '@signozhq/icons';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { Button } from '@signozhq/ui/button';
const havingOperators = [
{
@@ -368,9 +368,12 @@ function HavingFilter({
}}
/>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={onClose}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Radio, RadioChangeEvent, Tooltip } from 'antd';
import { Radio, RadioChangeEvent, Tooltip } from 'antd';
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GroupByFilter } from 'container/QueryBuilder/filters/GroupByFilter/GroupByFilter';
@@ -17,6 +17,7 @@ import HavingFilter from './HavingFilter/HavingFilter';
import { buildDefaultLegendFromGroupBy } from './utils';
import './QueryAddOns.styles.scss';
import { Button } from '@signozhq/ui/button';
interface AddOn {
icon: React.ReactNode;
@@ -283,14 +284,14 @@ function QueryAddOns({
const handleChangeGroupByKeys = useCallback(
(value: IBuilderQuery['groupBy']) => {
handleChangeQueryData('groupBy', value, { runAfterUpdate: true });
handleChangeQueryData('groupBy', value);
},
[handleChangeQueryData],
);
const handleChangeOrderByKeys = useCallback(
(value: IBuilderQuery['orderBy']) => {
handleChangeQueryData('orderBy', value, { runAfterUpdate: true });
handleChangeQueryData('orderBy', value);
},
[handleChangeQueryData],
);
@@ -370,9 +371,12 @@ function QueryAddOns({
/>
</div>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={(): void => handleRemoveView('group_by')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
</div>
</div>
@@ -455,9 +459,12 @@ function QueryAddOns({
</div>
{!isListViewPanel && (
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={(): void => handleRemoveView('order_by')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
)}
</div>
@@ -488,9 +495,12 @@ function QueryAddOns({
</div>
<Button
className="close-btn periscope-btn ghost"
icon={<ChevronUp size={16} />}
className="close-btn"
onClick={(): void => handleRemoveView('reduce_to')}
variant="outlined"
color="secondary"
size="icon"
prefix={<ChevronUp size={16} />}
/>
</div>
</div>

View File

@@ -23,7 +23,7 @@ import CodeMirror, {
ViewPlugin,
ViewUpdate,
} from '@uiw/react-codemirror';
import { Button, Popover, Tooltip } from 'antd';
import { Popover, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { QUERY_BUILDER_KEY_TYPES } from 'constants/antlrQueryConstants';
import { QueryBuilderKeys } from 'constants/queryBuilder';
@@ -36,6 +36,7 @@ import { TracesAggregatorOperator } from 'types/common/queryBuilder';
import { useQueryBuilderV2Context } from '../../QueryBuilderV2Context';
import './QueryAggregation.styles.scss';
import { Button } from '@signozhq/ui/button';
const chipDecoration = Decoration.mark({
class: 'chip-decorator',
@@ -720,9 +721,10 @@ function QueryAggregationSelect({
overlayClassName="query-aggregation-error-popover"
>
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost query-aggregation-error-btn"
className="query-aggregation-error-btn"
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
/>
</Popover>
</div>

View File

@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Button } from '@signozhq/ui/button';
import WarningPopover from 'components/WarningPopover/WarningPopover';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -56,9 +57,11 @@ function TraceOperatorSection({
}
>
<Button
className="add-trace-operator-button periscope-btn"
icon={<DraftingCompass size={16} />}
className="add-trace-operator-button"
onClick={(): void => addTraceOperator?.()}
variant="outlined"
color="secondary"
prefix={<DraftingCompass size={16} />}
>
<div className="qb-trace-operator-button-container-text">
Add Trace Matching
@@ -92,9 +95,12 @@ export default function QueryFooter({
<div className="qb-add-new-query">
<Tooltip title={<div style={{ textAlign: 'center' }}>Add New Query</div>}>
<Button
className="add-new-query-button periscope-btn "
icon={<Plus size={16} />}
className="add-new-query-button"
onClick={addNewBuilderQuery}
variant="outlined"
color="secondary"
size="icon"
prefix={<Plus size={16} />}
/>
</Tooltip>
</div>
@@ -118,9 +124,11 @@ export default function QueryFooter({
}
>
<Button
className="add-formula-button periscope-btn "
icon={<Sigma size={16} />}
className="add-formula-button"
onClick={addNewFormula}
variant="outlined"
color="secondary"
prefix={<Sigma size={16} />}
>
Add Formula
</Button>

View File

@@ -14,7 +14,7 @@ import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror';
import { Button, Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
@@ -49,6 +49,7 @@ import { queryExamples } from './constants';
import { combineInitialAndUserExpression } from './utils';
import './QuerySearch.styles.scss';
import { Button } from '@signozhq/ui/button';
const { Panel } = Collapse;
@@ -1484,15 +1485,17 @@ function QuerySearch({
>
{validation.isValid ? (
<Button
type="text"
icon={<CircleCheck size="md" />}
className="periscope-btn ghost"
variant="ghost"
color="secondary"
size="icon"
prefix={<CircleCheck size={14} />}
/>
) : (
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost"
color="destructive"
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} />}
/>
)}
</Popover>

View File

@@ -73,8 +73,8 @@ export const QueryV2 = forwardRef(function QueryV2(
});
const handleToggleDisableQuery = useCallback(() => {
handleChangeQueryData('disabled', !query.disabled, { runAfterUpdate: true });
}, [handleChangeQueryData, query.disabled]);
handleChangeQueryData('disabled', !query.disabled);
}, [handleChangeQueryData, query]);
const handleToggleCollapsQuery = (): void => {
setIsCollapsed(!isCollapsed);

View File

@@ -1,5 +1,6 @@
import { useCallback } from 'react';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -108,7 +109,7 @@ export default function TraceOperator({
)}
</div>
<Tooltip title="Remove Trace Operator" placement="topLeft">
<Button className="periscope-btn ghost" onClick={removeTraceOperator}>
<Button onClick={removeTraceOperator} variant="outlined" color="secondary">
<Trash2 size={14} />
</Button>
</Tooltip>

View File

@@ -15,7 +15,7 @@ import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror';
import { Button, Popover } from 'antd';
import { Popover } from 'antd';
import cx from 'classnames';
import {
TRACE_OPERATOR_OPERATORS,
@@ -34,6 +34,7 @@ import { getInvolvedQueriesInTraceOperator } from './utils/utils';
import '../QuerySearch/QuerySearch.styles.scss';
import { CircleCheck, TriangleAlert } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
// Custom extension to stop events
const stopEventsExtension = EditorView.domEventHandlers({
@@ -465,15 +466,16 @@ function TraceOperatorEditor({
>
{validation.isValid ? (
<Button
type="text"
icon={<CircleCheck size="md" />}
className="periscope-btn ghost"
variant="ghost"
color="secondary"
size="icon"
prefix={<CircleCheck size={14} />}
/>
) : (
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost"
variant="ghost"
size="icon"
prefix={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
/>
)}
</Popover>

View File

@@ -1,6 +1,7 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Fragment, useMemo, useState } from 'react';
import { Button, Checkbox, Input, Skeleton } from 'antd';
import { Checkbox, Input, Skeleton } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
@@ -660,14 +661,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
{String(value)}
</Typography.Text>
)}
<Button type="text" className="only-btn">
<Button className="only-btn" variant="ghost" color="secondary">
{isSomeFilterPresentForCurrentAttribute
? currentFilterState[value] && !isMultipleValuesTrueForTheKey
? 'All'
: 'Only'
: 'Only'}
</Button>
<Button type="text" className="toggle-btn">
<Button className="toggle-btn" variant="ghost" color="secondary">
Toggle
</Button>
</div>

View File

@@ -1,7 +1,7 @@
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import EmptyQuickFilterIcon from 'assets/CustomIcons/EmptyQuickFilterIcon';
import { ArrowUpRight } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
const QUICK_FILTER_DOC_PATHS: Record<string, string> = {
severity_text: 'severity-text',
@@ -39,9 +39,9 @@ function LogsQuickFilterEmptyState({
</div>
</div>
<Button
type="link"
className="go-to-docs__button"
onClick={handleLearnMoreClick}
variant="link"
>
<div className="go-to-docs__button-text">Learn more</div>
<ArrowUpRight size={14} color={Color.BG_ROBIN_400} />

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Button, Collapse } from 'antd';
import { Collapse } from 'antd';
import {
IQuickFiltersConfig,
QuickFiltersSource,
@@ -21,6 +21,7 @@ import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
import './Duration.styles.scss';
import { Button } from '@signozhq/ui/button';
export type FilterType = Record<
AllTraceFilterKeys,
@@ -299,9 +300,9 @@ function Duration({
/>
{activeKeys.includes('durationNano') && (
<Button
type="link"
onClick={onClearHandler}
data-testid="collapse-duration-clearBtn"
variant="link"
>
Clear All
</Button>

View File

@@ -14,10 +14,10 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button } from 'antd';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { GripVertical } from '@signozhq/icons';
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
import { Button } from '@signozhq/ui/button';
function SortableFilter({
filter,
@@ -50,11 +50,13 @@ function SortableFilter({
</div>
{allowRemove && (
<Button
className="remove-filter-btn periscope-btn"
size="small"
className="remove-filter-btn"
onClick={(): void => {
onRemove(filter as FilterType);
}}
size="sm"
variant="outlined"
color="secondary"
>
Remove
</Button>

View File

@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { Button, Skeleton } from 'antd';
import { Skeleton } from 'antd';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { SIGNAL_DATA_SOURCE_MAP } from 'components/QuickFilters/QuickFiltersSettings/constants';
import { SignalType } from 'components/QuickFilters/types';
@@ -12,6 +12,7 @@ import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
import { DataSource } from 'types/common/queryBuilder';
import { Button } from '@signozhq/ui/button';
function OtherFiltersSkeleton(): JSX.Element {
return (
@@ -146,9 +147,11 @@ function OtherFilters({
<div key={filter.key} className="qf-filter-item other-filters-item">
<div className="qf-filter-key">{filter.key}</div>
<Button
className="add-filter-btn periscope-btn"
size="small"
className="add-filter-btn"
onClick={(): void => handleAddFilter(filter as FilterType)}
size="sm"
variant="outlined"
color="secondary"
>
Add
</Button>

View File

@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { Button, Input } from 'antd';
import { Input } from 'antd';
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
@@ -9,6 +9,7 @@ import useQuickFilterSettings from './hooks/useQuickFilterSettings';
import OtherFilters from './OtherFilters';
import './QuickFiltersSettings.styles.scss';
import { Button } from '@signozhq/ui/button';
function QuickFiltersSettings({
signal,
@@ -86,17 +87,17 @@ function QuickFiltersSettings({
{hasUnsavedChanges && (
<div className="qf-footer">
<Button
type="default"
onClick={handleDiscardChanges}
icon={<X size={16} />}
variant="outlined"
color="secondary"
prefix={<X size={16} />}
>
Discard
</Button>
<Button
type="primary"
onClick={handleSaveChanges}
icon={<Check size={16} />}
loading={isUpdatingCustomFilters}
prefix={<Check size={16} />}
>
Save changes
</Button>

View File

@@ -1,16 +1,14 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import refreshPaymentStatus from 'api/v3/licenses/put';
import cx from 'classnames';
import { RefreshCcw } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
import { Button } from '@signozhq/ui/button';
function RefreshPaymentStatus({
btnShape,
type,
}: {
btnShape?: 'default' | 'round' | 'circle';
type?: 'button' | 'text' | 'tooltip';
}): JSX.Element {
const { t } = useTranslation(['failedPayment']);
@@ -35,12 +33,11 @@ function RefreshPaymentStatus({
<span className="refresh-payment-status-btn-wrapper">
<Tooltip title={type === 'tooltip' ? t('refreshPaymentStatus') : ''}>
<Button
type={type === 'text' ? 'text' : 'default'}
shape={btnShape}
className={cx('periscope-btn', { text: type === 'text' })}
onClick={handleRefreshPaymentStatus}
icon={<RefreshCcw size={14} />}
loading={isLoading}
variant="outlined"
color="secondary"
prefix={<RefreshCcw size={14} />}
>
{type !== 'tooltip' ? t('refreshPaymentStatus') : ''}
</Button>
@@ -49,7 +46,6 @@ function RefreshPaymentStatus({
);
}
RefreshPaymentStatus.defaultProps = {
btnShape: 'default',
type: 'button',
};

View File

@@ -4,7 +4,7 @@ import type {
TableColumnsType as ColumnsType,
TableColumnType as ColumnType,
} from 'antd';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import { Dropdown, Flex, MenuProps, Switch } from 'antd';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -21,6 +21,7 @@ import {
} from './utils';
import './DynamicColumnTable.syles.scss';
import { Button } from '@signozhq/ui/button';
function DynamicColumnTable({
tablesource,
@@ -133,9 +134,11 @@ function DynamicColumnTable({
>
<Button
className="dynamicColumnTable-button filter-btn"
size="middle"
icon={<SlidersHorizontal size={14} />}
data-testid="additional-filters-button"
variant="outlined"
color="secondary"
size="icon"
prefix={<SlidersHorizontal size={14} />}
/>
</Dropdown>
)}

View File

@@ -127,7 +127,6 @@ function KeyFormPhase({
>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"

View File

@@ -190,7 +190,6 @@ function EditKeyForm({
>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"

View File

@@ -74,7 +74,7 @@
}
.ant-modal-footer {
.ant-btn {
button {
display: flex;
align-items: center;
gap: 4px;

View File

@@ -1,9 +1,10 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import { Check, Plus, X } from '@signozhq/icons';
import { Button, Flex, Tag } from 'antd';
import { Flex, Tag } from 'antd';
import Input from 'components/Input';
import './Tags.styles.scss';
import { Button } from '@signozhq/ui/button';
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
@@ -71,19 +72,19 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
<div className="confirm-cancel-actions">
<Button
type="primary"
className="periscope-btn"
size="small"
icon={<Check size={14} />}
onClick={handleInputConfirm}
size="sm"
prefix={<Check size={14} />}
variant="outlined"
color="secondary"
/>
<Button
type="primary"
className="periscope-btn"
size="small"
icon={<X size={14} />}
onClick={hideInput}
size="sm"
prefix={<X size={14} />}
variant="outlined"
color="secondary"
/>
</div>
</div>
@@ -91,15 +92,14 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
{!inputVisible && (
<Button
type="primary"
size="small"
style={{
fontSize: '11px',
}}
onClick={showInput}
size="sm"
prefix={<Plus size={14} />}
>
<Flex justify="center" align="center" gap={4}>
<Plus size="md" />
New Tag
</Flex>
</Button>

View File

@@ -626,6 +626,10 @@ function TanStackTableInner<TData>(
onChange={(value): void => {
setLimit(+value);
pagination.onLimitChange?.(+value);
if (page !== 1) {
setPage(1);
pagination.onPageChange?.(1);
}
}}
items={paginationPageSizeItems}
/>

View File

@@ -401,6 +401,62 @@ describe('TanStackTableView Integration', () => {
expect(onLimitChange).toHaveBeenCalledWith(20);
});
});
it('resets page to 1 when limit changes', async () => {
const user = userEvent.setup();
const onUrlUpdate = jest.fn<void, [UrlUpdateEvent]>();
const onPageChange = jest.fn();
renderTanStackTable({
props: {
pagination: { total: 100, defaultPage: 1, defaultLimit: 10, onPageChange },
enableQueryParams: true,
},
onUrlUpdate,
});
await waitFor(() => {
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
// Navigate to page 2
const nav = screen.getByRole('navigation');
const page2 = Array.from(nav.querySelectorAll('button')).find(
(btn) => btn.textContent?.trim() === '2',
);
if (!page2) {
throw new Error('Page 2 button not found in pagination');
}
await user.click(page2);
await waitFor(() => {
const lastPage = onUrlUpdate.mock.calls
.map((call) => call[0].searchParams.get('page'))
.filter(Boolean)
.pop();
expect(lastPage).toBe('2');
});
// Change page size
const comboboxTrigger = document.querySelector(
'button[aria-haspopup="dialog"]',
) as HTMLElement;
await user.click(comboboxTrigger);
const option20 = await screen.findByRole('option', { name: '20' });
await user.click(option20);
// Verify page reset to 1 (nuqs removes default values from URL)
await waitFor(() => {
const lastCall = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1];
const lastPage = lastCall[0].searchParams.get('page');
expect(lastPage === '1' || lastPage === null).toBe(true);
expect(lastCall[0].searchParams.get('limit')).toBe('20');
});
// Verify onPageChange callback was called with 1
expect(onPageChange).toHaveBeenCalledWith(1);
});
});
describe('sorting', () => {

View File

@@ -1,6 +1,7 @@
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { ChevronDown, Globe } from '@signozhq/icons';
import { Button, Dropdown } from 'antd';
import { Dropdown } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import TimeItems, {
timePreferance,
@@ -40,7 +41,7 @@ function TimePreference({
className="time-selection-target"
trigger={['click']}
>
<Button>
<Button variant="outlined" color="secondary">
<div className="button-selected-text">
<Globe size={14} />
<Typography.Text className="selected-value">

View File

@@ -29,7 +29,6 @@ const ROUTES = {
ALERTS_NEW: '/alerts/new',
ALERT_HISTORY: '/alerts/history',
ALERT_OVERVIEW: '/alerts/overview',
ALERT_TYPE_SELECTION: '/alerts/type-selection',
ALL_CHANNELS: '/settings/channels',
CHANNELS_NEW: '/settings/channels/new',
CHANNELS_EDIT: '/settings/channels/edit/:channelId',

View File

@@ -161,6 +161,7 @@ export default function AIAssistantModal(): JSX.Element | null {
<TooltipSimple title={showHistory ? 'Back to chat' : 'Conversations'}>
<Button
variant="ghost"
color="secondary"
size="icon"
onClick={(): void => setShowHistory((v) => !v)}
aria-label="Toggle conversations"
@@ -173,6 +174,7 @@ export default function AIAssistantModal(): JSX.Element | null {
<TooltipSimple title="New conversation">
<Button
variant="ghost"
color="secondary"
size="icon"
onClick={handleNew}
aria-label="New conversation"
@@ -184,6 +186,7 @@ export default function AIAssistantModal(): JSX.Element | null {
<TooltipSimple title="Open full screen">
<Button
variant="ghost"
color="secondary"
size="icon"
onClick={handleExpand}
disabled={!activeConversationId}
@@ -196,6 +199,7 @@ export default function AIAssistantModal(): JSX.Element | null {
<TooltipSimple title="Minimize to side panel">
<Button
variant="ghost"
color="secondary"
size="icon"
onClick={handleMinimize}
aria-label="Minimize to side panel"
@@ -207,6 +211,7 @@ export default function AIAssistantModal(): JSX.Element | null {
<TooltipSimple title="Close">
<Button
variant="ghost"
color="secondary"
size="icon"
onClick={closeModal}
aria-label="Close"

View File

@@ -738,6 +738,7 @@ export default function ChatInput({
<span className={styles.attachmentName}>{f.name}</span>
<Button
variant="ghost"
color="secondary"
size="icon"
className={styles.attachmentRemove}
onClick={(): void => removeFile(f.uid)}
@@ -1007,6 +1008,7 @@ export default function ChatInput({
<TooltipSimple title="Voice input">
<Button
variant="ghost"
color="secondary"
size="icon"
onClick={(): void => startVoiceInput('button')}
disabled={disabled}

View File

@@ -1,7 +1,6 @@
import { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import history from 'lib/history';
import { ArrowRight } from '@signozhq/icons';
@@ -11,6 +10,7 @@ import { TopContributorsCardProps } from './types';
import ViewAllDrawer from './ViewAllDrawer';
import './TopContributorsCard.styles.scss';
import { Button } from '@signozhq/ui/button';
function TopContributorsCard({
topContributorsData,
@@ -52,7 +52,12 @@ function TopContributorsCard({
<div className="top-contributors-card__header">
<div className="title">top contributors</div>
{topContributorsData.length > 3 && (
<Button type="text" className="view-all" onClick={toggleViewAllDrawer}>
<Button
className="view-all"
onClick={toggleViewAllDrawer}
variant="ghost"
color="secondary"
>
<div className="label">View all</div>
<div className="icon">
<ArrowRight

View File

@@ -1,7 +1,6 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { Button } from 'antd';
import type { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes';
@@ -12,6 +11,7 @@ import { useAppContext } from 'providers/App/App';
import { Channels } from 'types/api/channels/getAll';
import Delete from './Delete';
import { Button } from '@signozhq/ui/button';
function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
const { t } = useTranslation(['channels']);
@@ -51,7 +51,7 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
width: 80,
render: (id: string): JSX.Element => (
<>
<Button onClick={(): void => onClickEditHandler(id)} type="link">
<Button onClick={(): void => onClickEditHandler(id)} variant="link">
{t('column_channel_edit')}
</Button>
<Delete id={id} notifications={notifications} />

View File

@@ -1,10 +1,10 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { Button } from 'antd';
import type { NotificationInstance } from 'antd/es/notification/interface';
import deleteChannel from 'api/channels/delete';
import APIError from 'types/api/error';
import { Button } from '@signozhq/ui/button';
function Delete({ notifications, id }: DeleteProps): JSX.Element {
const { t } = useTranslation(['channels']);
@@ -38,8 +38,8 @@ function Delete({ notifications, id }: DeleteProps): JSX.Element {
<Button
loading={loading}
disabled={loading}
type="link"
onClick={onClickHandler}
variant="link"
>
Delete
</Button>

View File

@@ -1,3 +1,4 @@
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -5,7 +6,7 @@ import { useQueries } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Link, useLocation } from 'react-router-dom';
import { Button, Card, Input, Space, TableProps, Tooltip, Flex } from 'antd';
import { Card, Input, Space, TableProps, Tooltip, Flex } from 'antd';
import { Search } from '@signozhq/icons';
import type { ColumnType, TablePaginationConfig } from 'antd/es/table';
import type { FilterValue, SorterResult } from 'antd/es/table/interface';
@@ -266,9 +267,8 @@ function AllErrors(): JSX.Element {
onPressEnter={handleSearch(confirm, String(selectedKeys[0]), filterKey)}
/>
<Button
type="primary"
onClick={handleSearch(confirm, String(selectedKeys[0]), filterKey)}
size="small"
size="sm"
>
<Flex align="center" justify="center" gap={4}>
<Search size="md" />

View File

@@ -706,7 +706,7 @@
.views-tabs {
color: var(--l2-foreground);
.ant-btn {
button {
box-shadow: none;
position: relative;
}

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Spacing } from '@signozhq/design-tokens';
import { Button, Divider, Drawer, Radio } from 'antd';
import { Divider, Drawer, Radio } from 'antd';
import { Button, ButtonGroup } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import type { RadioChangeEvent } from 'antd/lib';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
@@ -176,7 +177,7 @@ function DomainDetails({
modalInitialStartTime={modalTimeRange.startTime * 1000}
modalInitialEndTime={modalTimeRange.endTime * 1000}
/>
<Button.Group className="domain-details-drawer-header-ctas">
<ButtonGroup className="domain-details-drawer-header-ctas">
<Button
className="domain-navigate-cta"
onClick={(): void => {
@@ -185,9 +186,12 @@ function DomainDetails({
setEndPointsGroupBy([]);
setSelectedView(VIEW_TYPES.ALL_ENDPOINTS);
}}
icon={<ArrowUp size={16} />}
disabled={selectedDomainIndex === 0}
title="Previous domain"
variant="outlined"
color="secondary"
size="icon"
prefix={<ArrowUp size={16} />}
/>
<Button
className="domain-navigate-cta"
@@ -197,11 +201,14 @@ function DomainDetails({
setEndPointsGroupBy([]);
setSelectedView(VIEW_TYPES.ALL_ENDPOINTS);
}}
icon={<ArrowDown size={16} />}
disabled={selectedDomainIndex === domainListLength - 1}
title="Next domain"
variant="outlined"
color="secondary"
size="icon"
prefix={<ArrowDown size={16} />}
/>
</Button.Group>
</ButtonGroup>
</div>
</div>
}

View File

@@ -1,4 +1,4 @@
import { Button } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { RotateCw } from '@signozhq/icons';
@@ -22,7 +22,9 @@ function ErrorState({ refetch }: { refetch: () => void }): JSX.Element {
<Button
className="refresh-cta"
onClick={(): void => refetch()}
icon={<RotateCw size={16} />}
variant="outlined"
color="secondary"
prefix={<RotateCw size={16} />}
>
Refresh this panel
</Button>

View File

@@ -1,7 +1,8 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Button, Card, Skeleton } from 'antd';
import { Card, Skeleton } from 'antd';
import { Button, ButtonGroup } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
@@ -247,23 +248,25 @@ function StatusCodeBarCharts({
<Card bordered className="endpoint-details-card">
<div className="header">
<Typography.Text>Call response status</Typography.Text>
<Button.Group className="views-tabs">
<ButtonGroup>
<Button
value={0}
className={currentWidgetInfoIndex === 0 ? 'selected_view tab' : 'tab'}
disabled={false}
onClick={(): void => setCurrentWidgetInfoIndex(0)}
variant="outlined"
color="secondary"
>
Number of calls
</Button>
<Button
value={1}
className={currentWidgetInfoIndex === 1 ? 'selected_view tab' : 'tab'}
onClick={(): void => setCurrentWidgetInfoIndex(1)}
variant="outlined"
color="secondary"
>
Latency
</Button>
</Button.Group>
</ButtonGroup>
</div>
<div className="graph-container" ref={graphRef}>
{renderCardContent(endPointStatusCodeBarChartsDataQuery)}

View File

@@ -1,3 +1,4 @@
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -6,7 +7,6 @@ import { CircleCheck, CloudDownload } from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import {
Alert,
Button,
Card,
Col,
Flex,
@@ -445,12 +445,11 @@ export default function BillingContainer(): JSX.Element {
</Flex>
<Flex gap={8}>
<Button
type="default"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
disabled={isLoading || isFetchingBillingData}
onClick={handleCsvDownload}
className="periscope-btn"
variant="outlined"
color="secondary"
>
<Flex align="center" justify="center" gap={4}>
<CloudDownload size="md" />
@@ -459,8 +458,6 @@ export default function BillingContainer(): JSX.Element {
</Button>
<Button
data-testid="header-billing-button"
type="primary"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
disabled={isLoading}
onClick={handleBilling}
@@ -584,8 +581,6 @@ export default function BillingContainer(): JSX.Element {
<Col span={4} style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
data-testid="upgrade-plan-button"
type="primary"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
onClick={handleBilling}
>

View File

@@ -1,11 +1,12 @@
import { memo, useMemo } from 'react';
import { ChevronLeft, ChevronRight } from '@signozhq/icons';
import { Button, Flex, Select } from 'antd';
import { Flex, Select } from 'antd';
import { DEFAULT_PER_PAGE_OPTIONS, Pagination } from 'hooks/queryPagination';
import { popupContainer } from 'utils/selectPopupContainer';
import { defaultSelectStyle } from './config';
import { Container } from './styles';
import { Button } from '@signozhq/ui/button';
function Controls({
offset = 0,
@@ -37,10 +38,10 @@ function Controls({
<Container>
<Button
loading={isLoading}
size="small"
type="link"
disabled={isPreviousDisabled}
onClick={handleNavigatePrevious}
size="sm"
variant="link"
>
<Flex align="center" gap="4px">
<ChevronLeft size={16} /> Previous
@@ -48,10 +49,10 @@ function Controls({
</Button>
<Button
loading={isLoading}
size="small"
type="link"
disabled={isNextDisabled}
onClick={handleNavigateNext}
size="sm"
variant="link"
>
<Flex align="center" gap="4px">
Next <ChevronRight size={16} />

View File

@@ -1,12 +1,23 @@
import ROUTES from 'constants/routes';
import * as usePrefillAlertConditions from 'container/FormAlertRules/usePrefillAlertConditions';
import AlertTypeSelectionPage from 'pages/AlertTypeSelection';
import CreateAlertPage from 'pages/CreateAlert';
import { act, fireEvent, render } from 'tests/test-utils';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { ALERT_TYPE_TO_TITLE, ALERT_TYPE_URL_MAP } from './constants';
jest.mock('react-router-dom-v5-compat', () => ({
...jest.requireActual('react-router-dom-v5-compat'),
useNavigationType: jest.fn(() => 'PUSH'),
useLocation: jest.fn(() => ({
pathname: '/alerts/new',
search: '',
hash: '',
state: null,
})),
useSearchParams: jest.fn(() => [new URLSearchParams(), jest.fn()]),
}));
jest
.spyOn(usePrefillAlertConditions, 'usePrefillAlertConditions')
.mockReturnValue({
@@ -54,20 +65,13 @@ describe('Alert rule documentation redirection', () => {
window.open = mockWindowOpen;
});
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.ALERT_TYPE_SELECTION}`,
}),
}));
beforeEach(() => {
act(() => {
renderResult = render(
<AlertTypeSelectionPage />,
<CreateAlertPage />,
{},
{
initialRoute: ROUTES.ALERT_TYPE_SELECTION,
initialRoute: ROUTES.ALERTS_NEW,
},
);
});

View File

@@ -15,6 +15,18 @@ jest.mock('react-router-dom', () => ({
}),
}));
jest.mock('react-router-dom-v5-compat', () => ({
...jest.requireActual('react-router-dom-v5-compat'),
useNavigationType: jest.fn(() => 'PUSH'),
useLocation: jest.fn(() => ({
pathname: '/alerts/new',
search: 'ruleType=anomaly_rule',
hash: '',
state: null,
})),
useSearchParams: jest.fn(() => [new URLSearchParams(), jest.fn()]),
}));
window.ResizeObserver =
window.ResizeObserver ||
jest.fn().mockImplementation(() => ({

View File

@@ -0,0 +1,75 @@
.create-alert-tabs {
&__extra {
display: flex;
align-items: center;
gap: 16px;
}
}
.create-alert-wrapper {
margin-top: 10px;
.divider {
border-color: var(--l1-border);
margin: 16px 0;
}
.breadcrumb-divider {
margin-top: 10px;
}
}
.create-alert__breadcrumb {
padding-left: 16px;
ol {
align-items: center;
}
.breadcrumb-item {
color: var(--l2-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: 0.25px;
padding: 0;
}
.ant-breadcrumb-separator,
.breadcrumb-item--last {
color: var(--muted-foreground);
font-family: 'Geist Mono';
}
}
.alerts-container {
.top-level-tab.periscope-tab {
padding: 2px 0;
}
.ant-tabs {
&-nav {
padding: 0 8px;
margin-bottom: 0 !important;
&::before {
border-bottom: 1px solid var(--l1-border) !important;
}
}
&-tab {
&[data-node-key='TriggeredAlerts'] {
margin-left: 16px;
}
&:not(:first-of-type) {
margin-left: 24px !important;
}
[aria-selected='false'] {
.periscope-tab {
color: var(--l2-foreground);
}
}
}
}
}

View File

@@ -0,0 +1,111 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { FeatureKeys } from 'constants/features';
import { getAppContextMockState } from 'container/RoutingPolicies/__tests__/testUtils';
import * as appHooks from 'providers/App/App';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import SelectAlertType from '..';
const useAppContextSpy = jest.spyOn(appHooks, 'useAppContext');
describe('SelectAlertType', () => {
const mockOnSelect = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
useAppContextSpy.mockReturnValue(getAppContextMockState());
});
it('should render all alert type options when anomaly detection is enabled', () => {
useAppContextSpy.mockReturnValue({
...getAppContextMockState({}),
featureFlags: [
{
name: FeatureKeys.ANOMALY_DETECTION,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
],
});
render(<SelectAlertType onSelect={mockOnSelect} />);
expect(screen.getByText('metric_based_alert')).toBeInTheDocument();
expect(screen.getByText('log_based_alert')).toBeInTheDocument();
expect(screen.getByText('traces_based_alert')).toBeInTheDocument();
expect(screen.getByText('exceptions_based_alert')).toBeInTheDocument();
expect(screen.getByText('anomaly_based_alert')).toBeInTheDocument();
});
it('should render all alert type options except anomaly based alert when anomaly detection is disabled', () => {
render(<SelectAlertType onSelect={mockOnSelect} />);
expect(screen.getByText('metric_based_alert')).toBeInTheDocument();
expect(screen.getByText('log_based_alert')).toBeInTheDocument();
expect(screen.getByText('traces_based_alert')).toBeInTheDocument();
expect(screen.getByText('exceptions_based_alert')).toBeInTheDocument();
expect(screen.queryByText('anomaly_based_alert')).not.toBeInTheDocument();
});
it('should call onSelect with metrics based alert type', () => {
render(<SelectAlertType onSelect={mockOnSelect} />);
fireEvent.click(screen.getByText('metric_based_alert'));
expect(mockOnSelect).toHaveBeenCalledWith(
AlertTypes.METRICS_BASED_ALERT,
false,
);
});
it('should call onSelect with anomaly based alert type', () => {
useAppContextSpy.mockReturnValue({
...getAppContextMockState({}),
featureFlags: [
{
name: FeatureKeys.ANOMALY_DETECTION,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
],
});
render(<SelectAlertType onSelect={mockOnSelect} />);
fireEvent.click(screen.getByText('anomaly_based_alert'));
expect(mockOnSelect).toHaveBeenCalledWith(
AlertTypes.ANOMALY_BASED_ALERT,
false,
);
});
it('should call onSelect with log based alert type', () => {
render(<SelectAlertType onSelect={mockOnSelect} />);
fireEvent.click(screen.getByText('log_based_alert'));
expect(mockOnSelect).toHaveBeenCalledWith(AlertTypes.LOGS_BASED_ALERT, false);
});
it('should call onSelect with traces based alert type', () => {
render(<SelectAlertType onSelect={mockOnSelect} />);
fireEvent.click(screen.getByText('traces_based_alert'));
expect(mockOnSelect).toHaveBeenCalledWith(
AlertTypes.TRACES_BASED_ALERT,
false,
);
});
it('should call onSelect with exceptions based alert type', () => {
render(<SelectAlertType onSelect={mockOnSelect} />);
fireEvent.click(screen.getByText('exceptions_based_alert'));
expect(mockOnSelect).toHaveBeenCalledWith(
AlertTypes.EXCEPTIONS_BASED_ALERT,
false,
);
});
});

View File

@@ -1,13 +1,37 @@
import { render, screen } from '@testing-library/react';
import { fireEvent, screen } from '@testing-library/react';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import { initialQueriesMap } from 'constants/queryBuilder';
import { getAppContextMockState } from 'container/RoutingPolicies/__tests__/testUtils';
import * as useCompositeQueryParamHooks from 'hooks/queryBuilder/useGetCompositeQueryParam';
import * as navigateHooks from 'hooks/useSafeNavigate';
import * as useUrlQueryHooks from 'hooks/useUrlQuery';
import * as appHooks from 'providers/App/App';
import { render } from 'tests/test-utils';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { DataSource } from 'types/common/queryBuilder';
import CreateAlertRule from '../index';
jest.mock('react-router-dom-v5-compat', () => ({
...jest.requireActual('react-router-dom-v5-compat'),
useNavigationType: jest.fn(() => 'PUSH'),
useLocation: jest.fn(() => ({
pathname: '/alerts/new',
search: '',
hash: '',
state: null,
})),
useSearchParams: jest.fn(() => [new URLSearchParams(), jest.fn()]),
}));
jest.mock('container/TopNav/DateTimeSelectionV2', () => ({
__esModule: true,
default: function MockDateTimeSelector(): JSX.Element {
return <div data-testid="datetime-selector">Mock DateTime Selector</div>;
},
}));
jest.mock('container/FormAlertRules', () => ({
__esModule: true,
default: function MockFormAlertRules({
@@ -48,10 +72,14 @@ const useCompositeQueryParamSpy = jest.spyOn(
'useGetCompositeQueryParam',
);
const useUrlQuerySpy = jest.spyOn(useUrlQueryHooks, 'default');
const useSafeNavigateSpy = jest.spyOn(navigateHooks, 'useSafeNavigate');
const useAppContextSpy = jest.spyOn(appHooks, 'useAppContext');
const mockSetUrlQuery = jest.fn();
const mockToString = jest.fn();
const mockGetUrlQuery = jest.fn();
const mockSafeNavigate = jest.fn();
const mockDeleteUrlQuery = jest.fn();
const FORM_ALERT_RULES_TEXT = 'Form Alert Rules';
const CREATE_ALERT_V2_TEXT = 'Create Alert V2';
@@ -63,8 +91,13 @@ describe('CreateAlertRule', () => {
set: mockSetUrlQuery,
toString: mockToString,
get: mockGetUrlQuery,
delete: mockDeleteUrlQuery,
} as Partial<URLSearchParams> as URLSearchParams);
useCompositeQueryParamSpy.mockReturnValue(initialQueriesMap.metrics);
useSafeNavigateSpy.mockReturnValue({
safeNavigate: mockSafeNavigate,
});
useAppContextSpy.mockReturnValue(getAppContextMockState());
});
it('should render classic flow when showClassicCreateAlertsPage is true', () => {
@@ -72,18 +105,53 @@ describe('CreateAlertRule', () => {
if (key === QueryParams.showClassicCreateAlertsPage) {
return 'true';
}
if (key === QueryParams.alertType) {
return AlertTypes.METRICS_BASED_ALERT;
}
return null;
});
render(<CreateAlertRule />);
expect(screen.getByText(FORM_ALERT_RULES_TEXT)).toBeInTheDocument();
});
it('should render new flow by default', () => {
mockGetUrlQuery.mockReturnValue(null);
it('should render new flow when alertType is provided', () => {
mockGetUrlQuery.mockImplementation((key: string) => {
if (key === QueryParams.alertType) {
return AlertTypes.METRICS_BASED_ALERT;
}
return null;
});
render(<CreateAlertRule />);
expect(screen.getByText(CREATE_ALERT_V2_TEXT)).toBeInTheDocument();
});
it('should render type selection when no alertType in URL and no compositeQuery', () => {
mockGetUrlQuery.mockReturnValue(null);
useCompositeQueryParamSpy.mockReturnValue(null);
render(<CreateAlertRule />);
expect(screen.queryByText(FORM_ALERT_RULES_TEXT)).not.toBeInTheDocument();
expect(screen.queryByText(CREATE_ALERT_V2_TEXT)).not.toBeInTheDocument();
});
it('should skip type selection and render alert form when compositeQuery is present', () => {
mockGetUrlQuery.mockReturnValue(null);
useCompositeQueryParamSpy.mockReturnValue({
...initialQueriesMap.metrics,
builder: {
...initialQueriesMap.metrics.builder,
queryData: [
{
...initialQueriesMap.metrics.builder.queryData[0],
dataSource: DataSource.METRICS,
},
],
},
});
render(<CreateAlertRule />);
expect(screen.getByText(CREATE_ALERT_V2_TEXT)).toBeInTheDocument();
expect(screen.getByText(AlertTypes.METRICS_BASED_ALERT)).toBeInTheDocument();
});
it('should render classic flow when ruleType is anomaly_rule even if showClassicCreateAlertsPage is not true', () => {
mockGetUrlQuery.mockImplementation((key: string) => {
if (key === QueryParams.showClassicCreateAlertsPage) {
@@ -111,8 +179,13 @@ describe('CreateAlertRule', () => {
expect(screen.getByText(AlertTypes.LOGS_BASED_ALERT)).toBeInTheDocument();
});
it('should use alertType from compositeQuery dataSource when alertType is not in URL', () => {
mockGetUrlQuery.mockReturnValue(null);
it('should use alertType from URL over compositeQuery dataSource', () => {
mockGetUrlQuery.mockImplementation((key: string) => {
if (key === QueryParams.alertType) {
return AlertTypes.LOGS_BASED_ALERT;
}
return null;
});
useCompositeQueryParamSpy.mockReturnValue({
...initialQueriesMap.metrics,
builder: {
@@ -127,14 +200,123 @@ describe('CreateAlertRule', () => {
});
render(<CreateAlertRule />);
expect(screen.getByText(CREATE_ALERT_V2_TEXT)).toBeInTheDocument();
expect(screen.getByText(AlertTypes.TRACES_BASED_ALERT)).toBeInTheDocument();
expect(screen.getByText(AlertTypes.LOGS_BASED_ALERT)).toBeInTheDocument();
});
it('should default to METRICS_BASED_ALERT when no alertType and no compositeQuery', () => {
mockGetUrlQuery.mockReturnValue(null);
useCompositeQueryParamSpy.mockReturnValue(null);
render(<CreateAlertRule />);
expect(screen.getByText(CREATE_ALERT_V2_TEXT)).toBeInTheDocument();
expect(screen.getByText(AlertTypes.METRICS_BASED_ALERT)).toBeInTheDocument();
describe('handleSelectType navigation', () => {
beforeEach(() => {
mockGetUrlQuery.mockReturnValue(null);
useCompositeQueryParamSpy.mockReturnValue(null);
});
it('should navigate with threshold alert params for metrics alert', () => {
render(<CreateAlertRule />);
fireEvent.click(screen.getByText('metric_based_alert'));
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.ruleType,
'threshold_rule',
);
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.alertType,
AlertTypes.METRICS_BASED_ALERT,
);
expect(mockSafeNavigate).toHaveBeenCalled();
});
it('should navigate with threshold alert params for logs alert', () => {
render(<CreateAlertRule />);
fireEvent.click(screen.getByText('log_based_alert'));
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.ruleType,
'threshold_rule',
);
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.alertType,
AlertTypes.LOGS_BASED_ALERT,
);
expect(mockSafeNavigate).toHaveBeenCalled();
});
it('should navigate with threshold alert params for traces alert', () => {
render(<CreateAlertRule />);
fireEvent.click(screen.getByText('traces_based_alert'));
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.ruleType,
'threshold_rule',
);
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.alertType,
AlertTypes.TRACES_BASED_ALERT,
);
expect(mockSafeNavigate).toHaveBeenCalled();
});
it('should navigate with threshold alert params for exceptions alert', () => {
render(<CreateAlertRule />);
fireEvent.click(screen.getByText('exceptions_based_alert'));
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.ruleType,
'threshold_rule',
);
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.alertType,
AlertTypes.EXCEPTIONS_BASED_ALERT,
);
expect(mockSafeNavigate).toHaveBeenCalled();
});
it('should navigate with anomaly detection params for anomaly alert', () => {
useAppContextSpy.mockReturnValue({
...getAppContextMockState({}),
featureFlags: [
{
name: FeatureKeys.ANOMALY_DETECTION,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
],
});
render(<CreateAlertRule />);
fireEvent.click(screen.getByText('anomaly_based_alert'));
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.ruleType,
'anomaly_rule',
);
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.alertType,
AlertTypes.METRICS_BASED_ALERT,
);
expect(mockSafeNavigate).toHaveBeenCalled();
});
it('should navigate even when showClassicCreateAlertsPage flag is present', () => {
mockGetUrlQuery.mockImplementation((key: string) => {
if (key === QueryParams.showClassicCreateAlertsPage) {
return 'true';
}
return null;
});
render(<CreateAlertRule />);
fireEvent.click(screen.getByText('metric_based_alert'));
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.ruleType,
'threshold_rule',
);
expect(mockSetUrlQuery).toHaveBeenCalledWith(
QueryParams.alertType,
AlertTypes.METRICS_BASED_ALERT,
);
expect(mockSafeNavigate).toHaveBeenCalled();
});
});
});

View File

@@ -208,3 +208,11 @@ export const ALERTS_VALUES_MAP: Record<AlertTypes, AlertDef> = {
[AlertTypes.TRACES_BASED_ALERT]: traceAlertDefaults,
[AlertTypes.EXCEPTIONS_BASED_ALERT]: exceptionAlertDefaults,
};
export const ALERT_TYPE_BREADCRUMB_TITLE: Record<AlertTypes, string> = {
[AlertTypes.ANOMALY_BASED_ALERT]: 'Anomaly-Based Alert',
[AlertTypes.METRICS_BASED_ALERT]: 'Metric-Based Alert',
[AlertTypes.LOGS_BASED_ALERT]: 'Log-Based Alert',
[AlertTypes.TRACES_BASED_ALERT]: 'Traces-Based Alert',
[AlertTypes.EXCEPTIONS_BASED_ALERT]: 'Exceptions-Based Alert',
};

View File

@@ -1,21 +1,34 @@
import { useMemo } from 'react';
import { Form } from 'antd';
import { useCallback, useEffect, useMemo } from 'react';
import { Form, Tabs, TabsProps } from 'antd';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/AlertHistory/ConfigureIcon';
import AlertBreadcrumb from 'components/AlertBreadcrumb';
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import CreateAlertV2 from 'container/CreateAlertV2';
import FormAlertRules, { AlertDetectionTypes } from 'container/FormAlertRules';
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { AlertListTabs } from 'pages/AlertList/types';
import { GalleryVerticalEnd, Pyramid } from '@signozhq/icons';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config';
import { ALERTS_VALUES_MAP } from './defaults';
import { ALERTS_VALUES_MAP, ALERT_TYPE_BREADCRUMB_TITLE } from './defaults';
import SelectAlertType from './SelectAlertType';
import './CreateAlertRule.styles.scss';
function CreateRules(): JSX.Element {
const [formInstance] = Form.useForm();
const compositeQuery = useGetCompositeQueryParam();
const queryParams = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const ruleTypeFromURL = queryParams.get(QueryParams.ruleType);
const alertTypeFromURL = queryParams.get(QueryParams.alertType);
@@ -23,6 +36,15 @@ function CreateRules(): JSX.Element {
const showClassicCreateAlertsPageFlag =
queryParams.get(QueryParams.showClassicCreateAlertsPage) === 'true';
const isTypeSelectionMode =
!alertTypeFromURL && !ruleTypeFromURL && !compositeQuery;
useEffect(() => {
if (isTypeSelectionMode) {
logEvent('Alert: New alert data source selection page visited', {});
}
}, [isTypeSelectionMode]);
const alertType = useMemo(() => {
if (ruleTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) {
return AlertTypes.ANOMALY_BASED_ALERT;
@@ -45,22 +67,142 @@ function CreateRules(): JSX.Element {
[alertType, version],
);
// Load old alerts flow always for anomaly based alerts and when showClassicCreateAlertsPage is true
if (
showClassicCreateAlertsPageFlag ||
alertType === AlertTypes.ANOMALY_BASED_ALERT
) {
return (
<FormAlertRules
alertType={alertType}
formInstance={formInstance}
initialValue={initialAlertValue}
ruleId=""
/>
);
}
const handleTabChange = useCallback(
(tab: string): void => {
queryParams.set('tab', tab);
queryParams.delete('subTab');
queryParams.delete('search');
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${queryParams.toString()}`);
},
[safeNavigate, queryParams],
);
return <CreateAlertV2 alertType={alertType} />;
const handleSelectType = useCallback(
(type: AlertTypes, newTab?: boolean): void => {
if (type === AlertTypes.ANOMALY_BASED_ALERT) {
queryParams.set(
QueryParams.ruleType,
AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
);
queryParams.set(QueryParams.alertType, AlertTypes.METRICS_BASED_ALERT);
} else {
queryParams.set(QueryParams.ruleType, AlertDetectionTypes.THRESHOLD_ALERT);
queryParams.set(QueryParams.alertType, type);
}
safeNavigate(`${ROUTES.ALERTS_NEW}?${queryParams.toString()}`, { newTab });
},
[queryParams, safeNavigate],
);
const alertContent = useMemo(() => {
if (isTypeSelectionMode) {
return <SelectAlertType onSelect={handleSelectType} />;
}
if (
showClassicCreateAlertsPageFlag ||
alertType === AlertTypes.ANOMALY_BASED_ALERT
) {
return (
<FormAlertRules
alertType={alertType}
formInstance={formInstance}
initialValue={initialAlertValue}
ruleId=""
/>
);
}
return <CreateAlertV2 alertType={alertType} />;
}, [
isTypeSelectionMode,
handleSelectType,
showClassicCreateAlertsPageFlag,
alertType,
formInstance,
initialAlertValue,
]);
const items: TabsProps['items'] = [
{
label: (
<div className="periscope-tab top-level-tab">
<GalleryVerticalEnd size={14} />
Triggered Alerts
</div>
),
key: AlertListTabs.TRIGGERED_ALERTS,
children: null,
},
{
label: (
<div className="periscope-tab top-level-tab">
<Pyramid size={14} />
Alert Rules
</div>
),
key: AlertListTabs.ALERT_RULES,
children: (
<div className="create-alert-wrapper">
<AlertBreadcrumb
className="create-alert__breadcrumb"
items={
isTypeSelectionMode
? [
{
title: 'Alert Rules',
route: `${ROUTES.LIST_ALL_ALERT}?tab=${AlertListTabs.ALERT_RULES}`,
},
{ title: 'Select Alert Type', isLast: true },
]
: [
{
title: 'Alert Rules',
route: `${ROUTES.LIST_ALL_ALERT}?tab=${AlertListTabs.ALERT_RULES}`,
},
{ title: 'Select Alert Type', route: ROUTES.ALERTS_NEW },
{
title: ALERT_TYPE_BREADCRUMB_TITLE[alertType],
isLast: true,
},
]
}
/>
{alertContent}
</div>
),
},
{
label: (
<div className="periscope-tab top-level-tab">
<ConfigureIcon width={14} height={14} />
Configuration
</div>
),
key: AlertListTabs.CONFIGURATION,
children: null,
},
];
return (
<Tabs
destroyInactiveTabPane
items={items}
activeKey={AlertListTabs.ALERT_RULES}
onChange={handleTabChange}
className="alerts-container create-alert-tabs"
tabBarExtraContent={
<div className="create-alert-tabs__extra">
<DateTimeSelector showAutoRefresh />
<HeaderRightSection
enableAnnouncements={false}
enableShare
enableFeedback
/>
</div>
}
/>
);
}
export default CreateRules;

View File

@@ -1,5 +1,5 @@
import { useQuery } from 'react-query';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import getAllChannels from 'api/channels/getAll';
import classNames from 'classnames';
import { ChartLine } from '@signozhq/icons';
@@ -16,6 +16,7 @@ import AnomalyThreshold from './AnomalyThreshold';
import { ANOMALY_TAB_TOOLTIP, THRESHOLD_TAB_TOOLTIP } from './constants';
import './styles.scss';
import { Button } from '@signozhq/ui/button';
function AlertCondition(): JSX.Element {
const { alertType, setAlertType } = useCreateAlertState();
@@ -82,6 +83,8 @@ function AlertCondition(): JSX.Element {
handleAlertTypeChange(tab.value as AlertTypes);
}
}}
variant="outlined"
color="secondary"
>
{tab.icon}
{tab.label}

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { Button, Select, Tooltip } from 'antd';
import { Select, Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -294,11 +295,11 @@ function AlertThreshold({
/>
))}
<Button
type="dashed"
icon={<Plus size={16} />}
onClick={addThreshold}
className="add-threshold-btn"
data-testid="add-threshold-button"
variant="dashed"
prefix={<Plus size={16} />}
>
Add Threshold
</Button>

View File

@@ -1,5 +1,6 @@
import { useMemo, useState } from 'react';
import { Button, Input, Select, Tooltip } from 'antd';
import { Input, Select, Tooltip } from 'antd';
import { Button, ButtonGroup } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import { CircleX, Trash } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
@@ -166,16 +167,18 @@ function ThresholdItem({
/>
<Tooltip title="Remove recovery threshold">
<Button
type="default"
icon={<Trash size={16} />}
onClick={removeRecoveryThreshold}
className="icon-btn"
data-testid="remove-recovery-threshold-button"
variant="outlined"
color="secondary"
size="icon"
prefix={<Trash size={16} />}
/>
</Tooltip>
</>
)}
<Button.Group>
<ButtonGroup>
{/* TODO: Add recovery threshold back once the functionality is implemented */}
{/* {!showRecoveryThreshold && (
<Tooltip title="Add recovery threshold">
@@ -190,15 +193,17 @@ function ThresholdItem({
{showRemoveButton && (
<Tooltip title="Remove threshold">
<Button
type="default"
icon={<CircleX size={16} />}
onClick={(): void => removeThreshold(threshold.id)}
className="icon-btn"
data-testid="remove-threshold-button"
variant="outlined"
color="secondary"
size="icon"
prefix={<CircleX size={16} />}
/>
</Tooltip>
)}
</Button.Group>
</ButtonGroup>
</div>
</div>
</div>

View File

@@ -333,7 +333,7 @@
}
.condensed-evaluation-settings-container {
.ant-btn {
button {
display: flex;
align-items: center;
min-width: 240px;

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