Compare commits

..

37 Commits

Author SHA1 Message Date
Vinícius Lourenço
220b3e547c fix(column-view): remove unused file 2026-04-22 12:50:50 -03:00
Vinícius Lourenço
fbb14bbf83 fix(tanstack-header-row): use our version of popover 2026-04-22 10:40:05 -03:00
Vinícius Lourenço
67d1b7bc35 Merge branch 'main' into refactor/table-component 2026-04-22 10:36:54 -03:00
Vinicius Lourenço
c64cd0b692 fix(alert-rules): disable clickhouse/prompql for anomaly detection (#11038)
* refactor(alert-rules): disable clickhouse/prompql for anomaly detection

* fix(query-section): try fix issue with timing/useEffect delay

* fix(getUploadChartData): ensure there's no way to crash due to bad sync state

* fix(get-upload-chart-data): render as empty instead of skip series to be more safer
2026-04-22 13:01:42 +00:00
Vikrant Gupta
484b22c12a chore(codeowner): add @therealpandey as codeowner (#11055)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2026-04-22 09:51:02 +00:00
Pandey
5b599babd5 refactor(ruler): migrate frontend to generated API clients for rules and downtime schedules (#10968)
* refactor(frontend): migrate deleteDowntimeSchedule to generated client

Replace useDeleteDowntimeSchedule with useDeleteDowntimeScheduleByID
from the generated API client. Update PlannedDowntime.tsx and
PlannedDowntimeutils.ts to use pathParams pattern.

* refactor(frontend): migrate all downtime schedule APIs to generated clients

Replace hand-written getAllDowntimeSchedules, createDowntimeSchedule,
updateDowntimeSchedule with generated useListDowntimeSchedules,
createDowntimeSchedule, updateDowntimeScheduleByID from Orval clients.

Update all type references from DowntimeSchedules/Recurrence to
RuletypesGettablePlannedMaintenanceDTO/RuletypesRecurrenceDTO.
Update tests to mock generated hooks.

* refactor(frontend): migrate testAlert API to generated testRule client

Replace direct testAlertApi call in FormAlertRules with generated
testRule function. Update useTestAlertRule hook to use generated client
while maintaining backward-compatible wrapper types for CreateAlertV2
context consumers.

* refactor(frontend): migrate createAlertRule API to generated createRule client

Update useCreateAlertRule hook to use generated createRule function.
Maintain backward-compatible SuccessResponse wrapper for CreateAlertV2
context consumers.

* refactor(frontend): migrate rules APIs to generated clients

Replace hand-written getAll, get, delete, patch imports with generated
listRules, getRuleByID, deleteRuleByID, patchRuleByID from Orval
clients. Use adapter wrappers in consumers that depend on the old
SuccessResponse envelope to minimize cascading type changes.

* refactor(frontend): extract getAll adapter to shared api/alerts/getAll.ts

Remove duplicated inline getAll adapter from ListAlertRules and
AlertRules components. Replace the old hand-written getAll.ts with a
thin adapter over the generated listRules client.

* refactor(frontend): switch to generated react-query hooks directly

Replace adapter wrappers with direct usage of generated hooks:
- useListRules() in ListAlertRules, AlertRules
- useGetRuleByID() in AlertDetails hooks, EditRules
- useCreateRule re-exported as useCreateAlertRule
- useTestRule re-exported as useTestAlertRule
- Update consumers to access data.data instead of data.payload
- Remove SuccessResponse envelope wrappers

* fix(frontend): fix accidental rename, cache fragmentation, and sort mutation

- Revert setRuletypesRecurrenceDTOType back to setRecurrenceType (find-replace artifact)
- Remove duplicate isError condition in AlertDetails guard
- Replace manual useQuery(listRules) with useListRules in PlannedDowntime and hooks
- Fix showErrorNotification import path in PlannedDowntimeutils
- Use spread before sort to avoid mutating react-query cache

* refactor(frontend): delete dead re-export wrappers for rules hooks

Remove api/alerts/getAll.ts, hooks/alerts/useCreateAlertRule.ts, and
hooks/alerts/useTestAlertRule.ts — all were single-line re-exports of
generated hooks with zero unique logic. Update the one consumer
(CreateAlertV2 context) and its test to import directly from the
generated services.

* refactor(frontend): delete dead API adapters, migrate save to generated hooks

Delete 14 unused hand-written API adapter files for alerts and planned
downtime. Migrate the 3 consumers of save.ts to call createRule and
updateRuleByID from generated services directly, removing the
create/update routing layer.

* refactor(frontend): migrate updateAlertRule to generated useUpdateRuleByID

Delete hooks/alerts/useUpdateAlertRule.ts and api/alerts/updateAlertRule.ts.
Migrate CreateAlertV2 context to use useUpdateRuleByID from generated
services. Expose ruleId on context so Footer can pass it to the mutation.

* refactor(frontend): integrate convertToApiError and error modal across rules/downtime

Replace all raw (error as any)?.message casts and generic "Something
went wrong" strings with proper error handling using convertToApiError
and showErrorModal. Query errors use convertToApiError for message
extraction, mutation errors show the rich error modal.

* fix(frontend): remove downtime ID number coercion, delete routing adapter

Delete DowntimeScheduleUpdatePayload and createEditDowntimeSchedule —
the form now calls createDowntimeSchedule/updateDowntimeScheduleByID
directly, keeping the string ID from the generated schema end-to-end
instead of coercing to number.

* fix(frontend): handle null rules from API to prevent infinite spinner

The generated schema allows rules?: ...[] | null. When the API returns
null, the component treated it as still-loading. Normalize null to []
via nullish coalescing and gate loading on data presence, not rules
truthiness.

* fix(frontend): update PlannedDowntime test mock to use generated rules hook

Replace deleted api/alerts/getAll mock with api/generated/services/rules
mock returning useListRules shape.

* fix(frontend): mock useErrorModal in Footer test to fix provider error

Footer now calls useErrorModal which requires ErrorModalProvider. Add
mock to the test since it imports render from @testing-library/react
directly instead of the custom test-utils wrapper.

* refactor(frontend): adopt IngestionSettings date pattern in PlannedDowntime

Replace all (x as unknown) as Date/string double casts with the
established codebase pattern: new Date() for writes to API, dayjs()
for reads. Removes String() wraps in PlannedDowntimeList in favor of
dayjs().toISOString(). Tests use new Date() instead of double casts.

* refactor(frontend): adopt generated rule types, drop as any casts

Switch reader components (ListAlertRules, Home/AlertRules, AlertDetails,
EditRules) to use RuletypesGettableRuleDTO directly from the generated
OpenAPI client. Writer callers cast once via `as unknown as
RuletypesPostableRuleDTO` at the API boundary. Also fixes a poll bug in
ListAlert.tsx that read the old refetchData.payload shape.

* test(frontend): satisfy newly-required condition/ruleType in filterAlerts mocks

After merging the backend schema tightening, RuletypesGettableRuleDTO
requires `condition` and `ruleType`. Update the test fixtures to
include placeholder values so they type-check against the stricter
shape.

* refactor(frontend): retire GettableAlert in favor of generated type

Status and AlertHeader consume RuletypesGettableRuleDTO directly from
the generated OpenAPI client. Delete types/api/alerts/{get,getAll,patch}.ts
now that nothing imports from them.

* fix(frontend): satisfy newly-required condition and timezone fields

After merging the ruler schema tightening, RuletypesRuleConditionDTO
now requires compositeQuery/op/matchType, AlertCompositeQuery requires
queries/panelType/queryType, and Schedule requires timezone. Update
the filterAlerts test fixtures to populate the nested condition, and
default timezone to an empty string in the downtime form payload so
the generated type is satisfied (backend still validates timezone is
non-empty).

* refactor(frontend): cast timezone to string instead of empty-string default

Antd's required validation on the timezone field blocks submission when
empty, so values.timezone is guaranteed non-undefined by the time
saveHanlder runs. `?? ''` silently substituted a value the backend
rejects; `as string` matches the existing pattern used for the same
field at initialization (line 299) and surfaces drift as a type error
instead of a 400.

* refactor(frontend): key the alert-not-found message on HTTP 404

Drop the brittle comparison against the raw Go "sql: no rows in result
set" string. With the backend now wrapping sql.ErrNoRows as
TypeNotFound, GET /api/v1/rules/{id} returns HTTP 404 on a missing ID
(matching the existing OpenAPI contract). Use APIError.getHttpStatusCode
instead, and delete errorMessageReceivedFromBackend.

* refactor(frontend): show backend error message directly on rule-fetch failure

Drop the improvedErrorMessage override that hid the backend's message
behind a generic "does not exist" string. With the backend now
returning a descriptive "rule with ID: <id> does not exist" on 404, the
APIError message is the right thing to render. Inline the remaining
button label and delete the now-empty constants module.

* refactor(frontend): drop duplicative AlertDetailsStatusRenderer

The inner component re-derived alertRuleDetails from the same response
as the parent, but via the stale .payload.data path that didn't exist
on the generated GetRuleByID200 shape — so it was silently feeding
undefined to AlertHeader. The any-typed data prop hid the error from
TypeScript during the migration to generated hooks.

The parent already derives alertRuleDetails correctly and gates on
isError/isLoading before the main render, so the inner component's
loading/error branches were unreachable. Inline AlertHeader directly.

* refactor(frontend): drop optional chains on fields now required by generated types

* refactor(frontend): drop Record cast in PlannedDowntime recurrence spread

* refactor(frontend): type formData seed as Partial in PlannedDowntimeForm

* refactor(frontend): type defautlInitialValues as Partial directly

* refactor(frontend): drop as unknown from copyAlert cast in clone flow

* refactor(frontend): add dataSourceForAlertType helper keyed on generated enum

* refactor(frontend): align defautlInitialValues createdBy with createdAt as undefined

* refactor(frontend): surface BE error via convertToApiError on save alert paths

* refactor(frontend): adopt renamed RuletypesRuleDTO and created/updated field names

* fix(frontend): re-hydrate downtime edit drawer when alerts list arrives async

The formatedInitialValues memo depended only on initialValues, so when the
edit drawer opened before useListRules resolved, getAlertOptionsFromIds
returned an empty array and the form's alertRules state stayed empty —
even though the sidecar AlertRuleTags rendered a tag via selectedTags.
Submitting then sent alertIds: [] or stale values, which is why the
"edit downtime → remove silenced alert → save → delete" cleanup flow
never actually cleared the server-side association.

Adds alertOptions to the deps so the memo recomputes once the rules
list arrives, letting the useEffect below re-hydrate both selectedTags
and the form state with the correct tags.

* chore(frontend): stop logging validateFields rejection from downtime form

Form.validateFields() rejects with {values, errorFields, outOfDate}
whenever a required field is empty (e.g. Ends on). antd already renders
the field-level error inline, so console.error adds no user-facing
value and pollutes Sentry / devtools with routine typos.

Replaces the try/catch+log with a noop .catch().

* refactor(frontend): add typed converters between local alert types and generated DTOs

* refactor(frontend): add patchRulePartial wrapper for Partial rule patches

* refactor(frontend): replace inline alert DTO casts with typed converters

* refactor(frontend): inline single-use handleMutationError in alert hooks

Replaces four call sites in AlertDetails/hooks.tsx with the same
showErrorModal(convertToApiError(...)) expression directly; the helper
added no semantics beyond the type cast it wrapped. The two pure
onError arrows end up byte-identical, so the delete hook carries an
eslint-disable for sonarjs/no-identical-functions (same pattern already
used in providers/QueryBuilder.tsx).

* chore: parity with expected enum values of match type and compare operator

* chore: remove unused Typography import

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2026-04-22 09:07:47 +00:00
primus-bot[bot]
3080c3c493 chore(release): bump to v0.120.0 (#11052)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-04-22 07:11:54 +00:00
Vinicius Lourenço
b0e9fbe24f test(organization-traces): try unflaky both tests (#11046)
* test(organization-traces): try unflaky both tests

* fix: improve test speeds

* fix: increase timeout

---------

Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.com>
2026-04-22 07:11:25 +00:00
Abhi kumar
aeb71469d9 fix: minor tooltip css fix (#11053) 2026-04-22 07:04:29 +00:00
Ashwin Bhatkal
1ff9a748ee refactor: rename selectedDashboard to dashboardData (#11040)
Renames the `selectedDashboard` store field (and related setters/getters
`setSelectedDashboard`, `getSelectedDashboard`) to `dashboardData` across
the frontend. Also renames incidental locals (`updatedSelectedDashboard`,
`mockSelectedDashboard`, and the ExportPanel's local `selectedDashboardId`).
2026-04-22 05:58:21 +00:00
Yunus M
dfa8625e3d refactor: update styles and components for consistency and improved UI (#11043)
Some checks failed
build-staging / staging (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
Release Drafter / update_release_draft (push) Has been cancelled
2026-04-21 19:58:42 +00:00
swapnil-signoz
a60f8551dd feat: adding missing AWS regions (#11039)
* feat: adding missing AWS regions

* feat: fe add new AWS regions for Mexico and Asia Pacific

* refactor: adding missing region in migration as well
2026-04-21 19:28:01 +00:00
Tushar Vats
e607908b29 fix: not_in expression in having input box (#11035)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* fix: not_in expression in having input box

* fix: revert spacing check
2026-04-21 17:05:35 +00:00
swapnil-signoz
210ac2e74b feat: adding migration AWS cloud integration regions config (#10983)
* feat: adding migration AWS cloud integration regions config

* refactor: removing raw queries

* refactor: using table expr for table name

* refactor: using updated AWS regions declaration

* refactor: cleanup

* refactor: update AWS region migration logic to use new configuration method

* refactor: adding aws regions in migration

---------

Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2026-04-21 16:53:55 +00:00
Ashwin Bhatkal
5971a9efbf feat: show related values section, always (#11032)
* feat: show related values section, always

* chore: resolve comments

* chore: add tests
2026-04-21 14:42:00 +00:00
Yunus M
d43f3de049 refactor: use new components from signozhq/ui (#11008)
* refactor: use new components from signozhq/ui

* chore: remove migration doc

* refactor: update test imports and improve dialog handling

* chore: add assignee in todo items

* fix: failing test cases

* refactor: update button imports and adjust prefixIcon to prefix in AWS integration components

* refactor: use correct variables and latest components

* fix: add missing semicolon

* fix: add missing semicolon

* chore: use Button from @signozhq/ui

* refactor: update DrawerWrapper mock to include footer prop in tests

* refactor: remove richColor prop from toast

* fix: failing test cases

* chore: fix styling issues

* test: update EditMemberDrawer mock to include footer prop in tests

* chore: clean up and enhance component styles; update button variants and layout properties

* chore: add @signozhq/resizable package and update imports in TraceDetailV2 component

* refactor: move verifyFiltersAndOrderBy function to a separate file for better organization
2026-04-21 13:46:28 +00:00
Ashwin Bhatkal
7917540662 fix: alerts creation for query types other than builder (#11030)
* fix: alerts creation for query types other than builder

* chore: add tests
2026-04-21 11:33:32 +00:00
Vinicius Lourenço
addb234c8c test(infra-monitoring): fix flaky test (#11021) 2026-04-21 11:06:49 +00:00
Prakhar Dewan
be6a663e4b refactor: migrate CreateFunnel input to @signozhq/ui (#10996)
Refs SigNoz#10615
2026-04-21 11:04:32 +00:00
Abhi kumar
ed17003329 fix: y-axis crosshair sync for tooltip (#10944)
Some checks failed
build-staging / staging (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
Release Drafter / update_release_draft (push) Has been cancelled
* chore: added changes for crosshair sync for tooltip

* chore: minor cleanup

* chore: updated the core structure

* chore: updated the types

* chore: minor cleanup

* chore: minor changes
2026-04-21 08:36:04 +00:00
Piyush Singariya
50b452080f chore: Removal of JSONDataType field (#10925)
* fix: tons of changes

* chore: remove redundent comparison

* ci: tests fixed

* fix: upgraded collector version

* fix: qbtoexpr tests

* fix: go sum

* chore: upgrade collector version v0.144.3-rc.4

* fix: tests

* ci: test fix

* revert: remove db binaries

* test: selectField tests added

* fix: added safeguards in plan generation

* fix: name changed to field_map

* fix: json access plan remval of AvailableTypes

* fix: invalid index usage on terminal condition

* fix: branches should tell missing array types

* fix: comment removed

* fix: issue with FuzzyMatching and API failing

* fix: int64 mapping

* ci: test and lint fix

* fix: test VisitKey

* test: running test for sku

* fix: buildFieldForJSON works

* fix: few minor changes

* fix: refactor tag vs field_key table

* fix: minor changes based on review

* revert: minor variable change

* fix: added more membership testcases

* revert: minor var names reverted

* ci: tests aligned

* fix: indexed expressions
2026-04-21 05:11:50 +00:00
Vinícius Lourenço
755231c200 fix(resizing): better resizing support 2026-04-20 14:44:13 -03:00
Vinícius Lourenço
35a14617a7 fix(sonner): removed old dependency 2026-04-20 09:06:52 -03:00
Vinícius Lourenço
a202e6d461 Merge branch 'main' into refactor/table-component 2026-04-20 08:58:05 -03:00
Vinícius Lourenço
a581219957 fix(tanstack): more cleanup 2026-04-15 21:36:39 -03:00
Vinícius Lourenço
538edd06d7 refactor(table): removed deprecated api 2026-04-15 21:25:57 -03:00
Vinícius Lourenço
f94456e249 refactor(table): more cleanup 2026-04-15 20:07:28 -03:00
Vinícius Lourenço
bf49db3501 refactor(table): cleanup and fixes related to column and old preferences of logs 2026-04-15 17:35:59 -03:00
Vinícius Lourenço
0acb1e0bed docs(tanstacktable): add more docs about usage 2026-04-14 22:29:58 -03:00
Vinícius Lourenço
5861718972 refactor(tanstack-table): refine component based on infra monitoring 2026-04-14 22:23:42 -03:00
Vinícius Lourenço
7d8c8e3d7d fix(row): missing active styles 2026-04-13 19:37:13 -03:00
Vinícius Lourenço
5eae936ab4 fix(tests): use find by text 2026-04-13 19:17:34 -03:00
Vinícius Lourenço
091d61045a fix(styles): minor fixes on styles 2026-04-13 19:15:43 -03:00
Vinícius Lourenço
ed1217e5d0 chore(tests): cleanup tests and components 2026-04-13 18:57:30 -03:00
Vinícius Lourenço
0389b46836 refactor(table): optimize 2026-04-13 18:36:45 -03:00
Vinícius Lourenço
284d6f72d4 refactor(table): extract table to own component 2026-04-13 15:49:33 -03:00
Vinícius Lourenço
66abfa3be4 refactor(tanstack): move table to components & convert to css modules 2026-04-08 15:27:59 -03:00
461 changed files with 10471 additions and 9134 deletions

8
.github/CODEOWNERS vendored
View File

@@ -10,6 +10,7 @@
/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx @makeavish
# CI
/deploy/ @therealpandey
.github @therealpandey
go.mod @therealpandey
@@ -108,6 +109,7 @@ go.mod @therealpandey
/pkg/modules/role/ @therealpandey
# IdentN Owners
/pkg/identn/ @therealpandey
/pkg/http/middleware/identn.go @therealpandey
@@ -115,6 +117,10 @@ go.mod @therealpandey
/tests/integration/ @therealpandey
# Flagger Owners
/pkg/flagger/ @therealpandey
# OpenAPI types generator
/frontend/src/api @SigNoz/frontend-maintainers
@@ -134,6 +140,7 @@ go.mod @therealpandey
/frontend/src/container/ListOfDashboard/ @SigNoz/pulse-frontend
# Dashboard Widget Page
/frontend/src/pages/DashboardWidget/ @SigNoz/pulse-frontend
/frontend/src/container/NewWidget/ @SigNoz/pulse-frontend
@@ -149,6 +156,7 @@ go.mod @therealpandey
/frontend/src/container/PublicDashboardContainer/ @SigNoz/pulse-frontend
## Dashboard Libs + Components
/frontend/src/lib/uPlotV2/ @SigNoz/pulse-frontend
/frontend/src/lib/dashboard/ @SigNoz/pulse-frontend
/frontend/src/lib/dashboardVariables/ @SigNoz/pulse-frontend

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.119.0
image: signoz/signoz:v0.120.0
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port
@@ -213,7 +213,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.2
image: signoz/signoz-otel-collector:v0.144.3
entrypoint:
- /bin/sh
command:
@@ -241,7 +241,7 @@ services:
replicas: 3
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.2
image: signoz/signoz-otel-collector:v0.144.3
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster

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.119.0
image: signoz/signoz:v0.120.0
ports:
- "8080:8080" # signoz port
volumes:
@@ -139,7 +139,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.2
image: signoz/signoz-otel-collector:v0.144.3
entrypoint:
- /bin/sh
command:
@@ -167,7 +167,7 @@ services:
replicas: 3
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.144.2
image: signoz/signoz-otel-collector:v0.144.3
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster

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.119.0}
image: signoz/signoz:${VERSION:-v0.120.0}
container_name: signoz
ports:
- "8080:8080" # signoz port
@@ -204,7 +204,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
container_name: signoz-otel-collector
entrypoint:
- /bin/sh
@@ -229,7 +229,7 @@ services:
- "4318:4318" # OTLP HTTP receiver
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
container_name: signoz-telemetrystore-migrator
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000

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.119.0}
image: signoz/signoz:${VERSION:-v0.120.0}
container_name: signoz
ports:
- "8080:8080" # signoz port
@@ -132,7 +132,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
container_name: signoz-otel-collector
entrypoint:
- /bin/sh
@@ -157,7 +157,7 @@ services:
- "4318:4318" # OTLP HTTP receiver
signoz-telemetrystore-migrator:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
container_name: signoz-telemetrystore-migrator
environment:
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000

View File

@@ -45,7 +45,8 @@ const config: Config.InitialOptions = {
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testPathIgnorePatterns: ['/node_modules/', '/public/'],

View File

@@ -24,6 +24,10 @@ window.matchMedia =
};
};
if (!HTMLElement.prototype.scrollIntoView) {
HTMLElement.prototype.scrollIntoView = function (): void {};
}
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),

View File

@@ -48,24 +48,10 @@
"@radix-ui/react-tooltip": "1.0.7",
"@sentry/react": "8.41.0",
"@sentry/vite-plugin": "2.22.6",
"@signozhq/button": "0.0.5",
"@signozhq/calendar": "0.1.1",
"@signozhq/callout": "0.0.4",
"@signozhq/checkbox": "0.0.4",
"@signozhq/combobox": "0.0.4",
"@signozhq/command": "0.0.2",
"@signozhq/design-tokens": "2.1.4",
"@signozhq/dialog": "0.0.4",
"@signozhq/drawer": "0.0.6",
"@signozhq/icons": "0.1.0",
"@signozhq/input": "0.0.4",
"@signozhq/popover": "0.1.2",
"@signozhq/radio-group": "0.0.4",
"@signozhq/resizable": "0.0.2",
"@signozhq/tabs": "0.0.11",
"@signozhq/table": "0.3.8",
"@signozhq/toggle-group": "0.0.3",
"@signozhq/ui": "0.0.5",
"@signozhq/ui": "0.0.9",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

View File

@@ -1,52 +0,0 @@
{
"page_title": "SigNoz MCP Server",
"page_subtitle": "Connect AI assistants like Claude, Cursor, VS Code, and Codex to your SigNoz data via the Model Context Protocol. Authenticate from your MCP client with a service-account API key.",
"fallback_title": "MCP Server is available on SigNoz Cloud",
"fallback_body": "This in-product setup guide is for SigNoz Cloud. Self-hosted users can follow the docs to run the MCP server against their instance.",
"fallback_docs_link": "View MCP Server docs",
"region_warning_prefix": "We couldn't detect your Cloud region from this URL. Find it at ",
"region_warning_link": "Settings → Ingestion",
"region_warning_suffix": " (the label between your workspace name and signoz.cloud in the ingestion URL), then enter it here.",
"region_input_label": "SigNoz Cloud region",
"region_input_placeholder": "Your SigNoz Cloud region",
"step1_title": "Configure your client",
"step1_description": "Add SigNoz to your MCP client. Use a one-click install where available, or copy the config for manual setup. On first connect, the client will open a SigNoz authorization page — use the instance URL and API key from step 2.",
"step1_manual_fallback": "Or copy the config below for manual setup.",
"step1_client_docs_suffix": " setup docs",
"step1_add_to_client_prefix": "Add to ",
"client_cursor_install_label": "Add to Cursor",
"client_vscode_install_label": "Add to VS Code",
"client_claude_desktop_instructions": "Open Claude Desktop, go to Settings → Connectors → Add custom connector, and paste the endpoint URL above. Claude Desktop does not read remote MCP servers from claude_desktop_config.json — the connector UI is the only supported path.",
"client_other_instructions": "Most MCP clients that support remote HTTP servers will accept the endpoint URL above. Add it as a new MCP server in your client and paste your SigNoz API key when the client prompts for authentication. See the docs for client-specific instructions.",
"step2_title": "Authenticate from your client",
"step2_description": "On first connect, your client opens a SigNoz authorization page asking for two values:",
"step2_instance_url_label": "SigNoz Instance URL",
"step2_api_key_label": "API Key",
"step2_admin_cta": "Create service account",
"step2_admin_helper": "Create a service account, then add a new key inside it — paste that key into the API Key field.",
"step2_viewer_helper": "Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.",
"use_cases_title": "What you can do with it",
"use_cases_item_1": "Ask your AI assistant to investigate a spiking error rate.",
"use_cases_item_2": "Debug a slow service by walking through recent traces.",
"use_cases_item_3": "Summarize an alert and suggest likely root causes.",
"use_cases_item_4": "Generate dashboards or queries from a natural-language description.",
"use_cases_docs_link": "See more use cases",
"copy_tooltip_enabled": "Copy to clipboard",
"copy_tooltip_disabled": "Enter your Cloud region first",
"copy_aria_endpoint": "Copy MCP endpoint",
"copy_aria_instance_url": "Copy SigNoz instance URL",
"copy_aria_snippet_prefix": "Copy ",
"copy_aria_snippet_suffix": " config",
"toast_endpoint_copied": "Endpoint copied to clipboard",
"toast_snippet_copied": "Snippet copied to clipboard",
"toast_instance_url_copied": "Instance URL copied to clipboard",
"toast_region_required": "Enter your Cloud region before copying"
}

View File

@@ -16,6 +16,5 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
"service_accounts": "Service Accounts"
}

View File

@@ -53,6 +53,5 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
}

View File

@@ -1,52 +0,0 @@
{
"page_title": "SigNoz MCP Server",
"page_subtitle": "Connect AI assistants like Claude, Cursor, VS Code, and Codex to your SigNoz data via the Model Context Protocol. Authenticate from your MCP client with a service-account API key.",
"fallback_title": "MCP Server is available on SigNoz Cloud",
"fallback_body": "This in-product setup guide is for SigNoz Cloud. Self-hosted users can follow the docs to run the MCP server against their instance.",
"fallback_docs_link": "View MCP Server docs",
"region_warning_prefix": "We couldn't detect your Cloud region from this URL. Find it at ",
"region_warning_link": "Settings → Ingestion",
"region_warning_suffix": " (the label between your workspace name and signoz.cloud in the ingestion URL), then enter it here.",
"region_input_label": "SigNoz Cloud region",
"region_input_placeholder": "Your SigNoz Cloud region",
"step1_title": "Configure your client",
"step1_description": "Add SigNoz to your MCP client. Use a one-click install where available, or copy the config for manual setup. On first connect, the client will open a SigNoz authorization page — use the instance URL and API key from step 2.",
"step1_manual_fallback": "Or copy the config below for manual setup.",
"step1_client_docs_suffix": " setup docs",
"step1_add_to_client_prefix": "Add to ",
"client_cursor_install_label": "Add to Cursor",
"client_vscode_install_label": "Add to VS Code",
"client_claude_desktop_instructions": "Open Claude Desktop, go to Settings → Connectors → Add custom connector, and paste the endpoint URL above. Claude Desktop does not read remote MCP servers from claude_desktop_config.json — the connector UI is the only supported path.",
"client_other_instructions": "Most MCP clients that support remote HTTP servers will accept the endpoint URL above. Add it as a new MCP server in your client and paste your SigNoz API key when the client prompts for authentication. See the docs for client-specific instructions.",
"step2_title": "Authenticate from your client",
"step2_description": "On first connect, your client opens a SigNoz authorization page asking for two values:",
"step2_instance_url_label": "SigNoz Instance URL",
"step2_api_key_label": "API Key",
"step2_admin_cta": "Create service account",
"step2_admin_helper": "Create a service account, then add a new key inside it — paste that key into the API Key field.",
"step2_viewer_helper": "Only admins can create API keys. Ask your workspace admin for a key with read access, then paste it into the API Key field.",
"use_cases_title": "What you can do with it",
"use_cases_item_1": "Ask your AI assistant to investigate a spiking error rate.",
"use_cases_item_2": "Debug a slow service by walking through recent traces.",
"use_cases_item_3": "Summarize an alert and suggest likely root causes.",
"use_cases_item_4": "Generate dashboards or queries from a natural-language description.",
"use_cases_docs_link": "See more use cases",
"copy_tooltip_enabled": "Copy to clipboard",
"copy_tooltip_disabled": "Enter your Cloud region first",
"copy_aria_endpoint": "Copy MCP endpoint",
"copy_aria_instance_url": "Copy SigNoz instance URL",
"copy_aria_snippet_prefix": "Copy ",
"copy_aria_snippet_suffix": " config",
"toast_endpoint_copied": "Endpoint copied to clipboard",
"toast_snippet_copied": "Snippet copied to clipboard",
"toast_instance_url_copied": "Instance URL copied to clipboard",
"toast_region_required": "Enter your Cloud region before copying"
}

View File

@@ -16,6 +16,5 @@
"roles": "Roles",
"role_details": "Role Details",
"members": "Members",
"service_accounts": "Service Accounts",
"mcp_server": "MCP Server"
"service_accounts": "Service Accounts"
}

View File

@@ -76,6 +76,5 @@
"METER": "SigNoz | Meter",
"ROLES_SETTINGS": "SigNoz | Roles",
"MEMBERS_SETTINGS": "SigNoz | Members",
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
"MCP_SERVER": "SigNoz | MCP Server"
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
}

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/create';
const create = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.post('/rules', {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default create;

View File

@@ -1,28 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
AlertRuleV2,
PostableAlertRuleV2,
} from 'types/api/alerts/alertTypesV2';
export interface CreateAlertRuleResponse {
data: AlertRuleV2;
status: string;
}
const createAlertRule = async (
props: PostableAlertRuleV2,
): Promise<SuccessResponse<CreateAlertRuleResponse> | ErrorResponse> => {
const response = await axios.post(`/rules`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default createAlertRule;

View File

@@ -1,18 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/delete';
const deleteAlerts = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.delete(`/rules/${props.id}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data.rules,
};
};
export default deleteAlerts;

View File

@@ -1,16 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/get';
const get = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.get(`/rules/${props.id}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
};
export default get;

View File

@@ -1,24 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/alerts/getAll';
const getAll = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
try {
const response = await axios.get('/rules');
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data.rules,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getAll;

View File

@@ -1,29 +0,0 @@
import { AxiosAlertManagerInstance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
const getGroups = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const queryParams = convertObjectIntoParams(props);
const response = await AxiosAlertManagerInstance.get(
`/alerts/groups?${queryParams}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getGroups;

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/patch';
const patch = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.patch(`/rules/${props.id}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default patch;

View File

@@ -0,0 +1,12 @@
import { patchRuleByID } from 'api/generated/services/rules';
import type { RuletypesPostableRuleDTO } from 'api/generated/services/sigNoz.schemas';
// why: patchRuleByID's generated body type is the full RuletypesPostableRuleDTO
// because the backend OpenAPI spec currently advertises PostableRule. The
// endpoint itself accepts any subset of fields. Until the backend introduces
// PatchableRule, this wrapper localizes the cast so callers stay typed.
export const patchRulePartial = (
id: string,
patch: Partial<RuletypesPostableRuleDTO>,
): ReturnType<typeof patchRuleByID> =>
patchRuleByID({ id }, patch as RuletypesPostableRuleDTO);

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/save';
const put = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.put(`/rules/${props.id}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default put;

View File

@@ -1,18 +0,0 @@
import { isEmpty } from 'lodash-es';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/save';
import create from './create';
import put from './put';
const save = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
if (props.id && !isEmpty(props.id)) {
return put({ ...props });
}
return create({ ...props });
};
export default save;

View File

@@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/alerts/testAlert';
const testAlert = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/testRule', {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default testAlert;

View File

@@ -1,28 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export interface TestAlertRuleResponse {
data: {
alertCount: number;
message: string;
};
status: string;
}
const testAlertRule = async (
props: PostableAlertRuleV2,
): Promise<SuccessResponse<TestAlertRuleResponse> | ErrorResponse> => {
const response = await axios.post(`/testRule`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default testAlertRule;

View File

@@ -1,26 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export interface UpdateAlertRuleResponse {
data: string;
status: string;
}
const updateAlertRule = async (
id: string,
postableAlertRule: PostableAlertRuleV2,
): Promise<SuccessResponse<UpdateAlertRuleResponse> | ErrorResponse> => {
const response = await axios.put(`/rules/${id}`, {
...postableAlertRule,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateAlertRule;

View File

@@ -1,45 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { Dayjs } from 'dayjs';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Recurrence } from './getAllDowntimeSchedules';
export interface DowntimeSchedulePayload {
name: string;
description?: string;
alertIds: string[];
schedule: {
timezone?: string;
startTime?: string | Dayjs;
endTime?: string | Dayjs;
recurrence?: Recurrence;
};
}
export interface PayloadProps {
status: string;
data: string;
}
const createDowntimeSchedule = async (
props: DowntimeSchedulePayload,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/downtime_schedules', {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default createDowntimeSchedule;

View File

@@ -1,19 +0,0 @@
import { useMutation, UseMutationResult } from 'react-query';
import axios from 'api';
export interface DeleteDowntimeScheduleProps {
id?: number;
}
export interface DeleteSchedulePayloadProps {
status: string;
data: string;
}
export const useDeleteDowntimeSchedule = (
props: DeleteDowntimeScheduleProps,
): UseMutationResult<DeleteSchedulePayloadProps, Error, number> =>
useMutation({
mutationKey: [props.id],
mutationFn: () => axios.delete(`/downtime_schedules/${props.id}`),
});

View File

@@ -1,51 +0,0 @@
import { useQuery, UseQueryResult } from 'react-query';
import axios from 'api';
import { AxiosError, AxiosResponse } from 'axios';
import { Option } from 'container/PlannedDowntime/PlannedDowntimeutils';
export type Recurrence = {
startTime?: string | null;
endTime?: string | null;
duration?: number | string | null;
repeatType?: string | Option | null;
repeatOn?: string[] | null;
};
type Schedule = {
timezone: string | null;
startTime: string | null;
endTime: string | null;
recurrence: Recurrence | null;
};
export interface DowntimeSchedules {
id: number;
name: string | null;
description: string | null;
schedule: Schedule | null;
alertIds: string[] | null;
createdAt: string | null;
createdBy: string | null;
updatedAt: string | null;
updatedBy: string | null;
kind: string | null;
}
export type PayloadProps = { data: DowntimeSchedules[] };
export const getAllDowntimeSchedules = async (
props?: GetAllDowntimeSchedulesPayloadProps,
): Promise<AxiosResponse<PayloadProps>> =>
axios.get('/downtime_schedules', { params: props });
export interface GetAllDowntimeSchedulesPayloadProps {
active?: boolean;
recurrence?: boolean;
}
export const useGetAllDowntimeSchedules = (
props?: GetAllDowntimeSchedulesPayloadProps,
): UseQueryResult<AxiosResponse<PayloadProps>, AxiosError> =>
useQuery<AxiosResponse<PayloadProps>, AxiosError>({
queryKey: ['getAllDowntimeSchedules', props],
queryFn: () => getAllDowntimeSchedules(props),
});

View File

@@ -1,37 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { DowntimeSchedulePayload } from './createDowntimeSchedule';
export interface DowntimeScheduleUpdatePayload {
data: DowntimeSchedulePayload;
id?: number;
}
export interface PayloadProps {
status: string;
data: string;
}
const updateDowntimeSchedule = async (
props: DowntimeScheduleUpdatePayload,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.put(`/downtime_schedules/${props.id}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default updateDowntimeSchedule;

View File

@@ -1,70 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 142.5 145.6" style="enable-background:new 0 0 142.5 145.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#565656;}
.st1{fill:url(#SVGID_1_);}
</style>
<g>
<path class="st0" d="M28.7,131.5c-0.3,7.9-6.6,14.1-14.4,14.1C6.1,145.6,0,139,0,130.9s6.6-14.7,14.7-14.7c3.6,0,7.2,1.6,10.2,4.4
l-2.3,2.9c-2.3-2-5.1-3.4-7.9-3.4c-5.9,0-10.8,4.8-10.8,10.8c0,6.1,4.6,10.8,10.4,10.8c5.2,0,9.3-3.8,10.2-8.8H12.6v-3.5h16.1
V131.5z"/>
<path class="st0" d="M42.3,129.5h-2.2c-2.4,0-4.4,2-4.4,4.4v11.4h-3.9v-19.6H35v1.6c1.1-1.1,2.7-1.6,4.6-1.6h4.2L42.3,129.5z"/>
<path class="st0" d="M63.7,145.3h-3.4v-2.5c-2.6,2.5-6.6,3.7-10.7,1.9c-3-1.3-5.3-4.1-5.9-7.4c-1.2-6.3,3.7-11.9,9.9-11.9
c2.6,0,5,1.1,6.7,2.8v-2.5h3.4V145.3z M59.7,137c0.9-4-2.1-7.6-6-7.6c-3.4,0-6.1,2.8-6.1,6.1c0,3.8,3.3,6.7,7.2,6.1
C57.1,141.2,59.1,139.3,59.7,137z"/>
<path class="st0" d="M71.5,124.7v1.1h6.2v3.4h-6.2v16.1h-3.8v-20.5c0-4.3,3.1-6.8,7-6.8h4.7l-1.6,3.7h-3.1
C72.9,121.6,71.5,123,71.5,124.7z"/>
<path class="st0" d="M98.5,145.3h-3.3v-2.5c-2.6,2.5-6.6,3.7-10.7,1.9c-3-1.3-5.3-4.1-5.9-7.4c-1.2-6.3,3.7-11.9,9.9-11.9
c2.6,0,5,1.1,6.7,2.8v-2.5h3.4v19.6H98.5z M94.5,137c0.9-4-2.1-7.6-6-7.6c-3.4,0-6.1,2.8-6.1,6.1c0,3.8,3.3,6.7,7.2,6.1
C92,141.2,93.9,139.3,94.5,137z"/>
<path class="st0" d="M119.4,133.8v11.5h-3.9v-11.6c0-2.4-2-4.4-4.4-4.4c-2.5,0-4.4,2-4.4,4.4v11.6h-3.9v-19.6h3.2v1.7
c1.4-1.3,3.3-2,5.2-2C115.8,125.5,119.4,129.2,119.4,133.8z"/>
<path class="st0" d="M142.4,145.3h-3.3v-2.5c-2.6,2.5-6.6,3.7-10.7,1.9c-3-1.3-5.3-4.1-5.9-7.4c-1.2-6.3,3.7-11.9,9.9-11.9
c2.6,0,5,1.1,6.7,2.8v-2.5h3.4v19.6H142.4z M138.4,137c0.9-4-2.1-7.6-6-7.6c-3.4,0-6.1,2.8-6.1,6.1c0,3.8,3.3,6.7,7.2,6.1
C135.9,141.2,137.8,139.3,138.4,137z"/>
</g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="71.25" y1="10.4893" x2="71.25" y2="113.3415" gradientTransform="matrix(1 0 0 -1 0 148.6)">
<stop offset="0" style="stop-color:#FCEE1F"/>
<stop offset="1" style="stop-color:#F15B2A"/>
</linearGradient>
<path class="st1" d="M122.9,49.9c-0.2-1.9-0.5-4.1-1.1-6.5c-0.6-2.4-1.6-5-2.9-7.8c-1.4-2.7-3.1-5.6-5.4-8.3
c-0.9-1.1-1.9-2.1-2.9-3.2c1.6-6.3-1.9-11.8-1.9-11.8c-6.1-0.4-9.9,1.9-11.3,2.9c-0.2-0.1-0.5-0.2-0.7-0.3c-1-0.4-2.1-0.8-3.2-1.2
c-1.1-0.3-2.2-0.7-3.3-0.9c-1.1-0.3-2.3-0.5-3.5-0.7c-0.2,0-0.4-0.1-0.6-0.1C83.5,3.6,75.9,0,75.9,0c-8.7,5.6-10.4,13.1-10.4,13.1
s0,0.2-0.1,0.4c-0.5,0.1-0.9,0.3-1.4,0.4c-0.6,0.2-1.3,0.4-1.9,0.7c-0.6,0.3-1.3,0.5-1.9,0.8c-1.3,0.6-2.5,1.2-3.8,1.9
c-1.2,0.7-2.4,1.4-3.5,2.2c-0.2-0.1-0.3-0.2-0.3-0.2c-11.7-4.5-22.1,0.9-22.1,0.9c-0.9,12.5,4.7,20.3,5.8,21.7
c-0.3,0.8-0.5,1.5-0.8,2.3c-0.9,2.8-1.5,5.7-1.9,8.7c-0.1,0.4-0.1,0.9-0.2,1.3c-10.8,5.3-14,16.3-14,16.3c9,10.4,19.6,11,19.6,11
l0,0c1.3,2.4,2.9,4.7,4.6,6.8c0.7,0.9,1.5,1.7,2.3,2.6c-3.3,9.4,0.5,17.3,0.5,17.3c10.1,0.4,16.7-4.4,18.1-5.5c1,0.3,2,0.6,3,0.9
c3.1,0.8,6.3,1.3,9.4,1.4c0.8,0,1.6,0,2.4,0h0.4H80h0.5H81l0,0c4.7,6.8,13.1,7.7,13.1,7.7c5.9-6.3,6.3-12.4,6.3-13.8l0,0
c0,0,0,0,0-0.1s0-0.2,0-0.2l0,0c0-0.1,0-0.2,0-0.3c1.2-0.9,2.4-1.8,3.6-2.8c2.4-2.1,4.4-4.6,6.2-7.2c0.2-0.2,0.3-0.5,0.5-0.7
c6.7,0.4,11.4-4.2,11.4-4.2c-1.1-7-5.1-10.4-5.9-11l0,0c0,0,0,0-0.1-0.1l-0.1-0.1l0,0l-0.1-0.1c0-0.4,0.1-0.8,0.1-1.3
c0.1-0.8,0.1-1.5,0.1-2.3v-0.6v-0.3v-0.1c0-0.2,0-0.1,0-0.2v-0.5v-0.6c0-0.2,0-0.4,0-0.6s0-0.4-0.1-0.6l-0.1-0.6l-0.1-0.6
c-0.1-0.8-0.3-1.5-0.4-2.3c-0.7-3-1.9-5.9-3.4-8.4c-1.6-2.6-3.5-4.8-5.7-6.8c-2.2-1.9-4.6-3.5-7.2-4.6c-2.6-1.2-5.2-1.9-7.9-2.2
c-1.3-0.2-2.7-0.2-4-0.2h-0.5h-0.1h-0.2h-0.2h-0.5c-0.2,0-0.4,0-0.5,0c-0.7,0.1-1.4,0.2-2,0.3c-2.7,0.5-5.2,1.5-7.4,2.8
c-2.2,1.3-4.1,3-5.7,4.9s-2.8,3.9-3.6,6.1c-0.8,2.1-1.3,4.4-1.4,6.5c0,0.5,0,1.1,0,1.6c0,0.1,0,0.3,0,0.4v0.4c0,0.3,0,0.5,0.1,0.8
c0.1,1.1,0.3,2.1,0.6,3.1c0.6,2,1.5,3.8,2.7,5.4s2.5,2.8,4,3.8s3,1.7,4.6,2.2c1.6,0.5,3.1,0.7,4.5,0.6c0.2,0,0.4,0,0.5,0
c0.1,0,0.2,0,0.3,0s0.2,0,0.3,0c0.2,0,0.3,0,0.5,0h0.1h0.1c0.1,0,0.2,0,0.3,0c0.2,0,0.4-0.1,0.5-0.1c0.2,0,0.3-0.1,0.5-0.1
c0.3-0.1,0.7-0.2,1-0.3c0.6-0.2,1.2-0.5,1.8-0.7c0.6-0.3,1.1-0.6,1.5-0.9c0.1-0.1,0.3-0.2,0.4-0.3c0.5-0.4,0.6-1.1,0.2-1.6
c-0.4-0.4-1-0.5-1.5-0.3C88,74,87.9,74,87.7,74.1c-0.4,0.2-0.9,0.4-1.3,0.5c-0.5,0.1-1,0.3-1.5,0.4c-0.3,0-0.5,0.1-0.8,0.1
c-0.1,0-0.3,0-0.4,0c-0.1,0-0.3,0-0.4,0s-0.3,0-0.4,0c-0.2,0-0.3,0-0.5,0c0,0-0.1,0,0,0h-0.1h-0.1c-0.1,0-0.1,0-0.2,0
s-0.3,0-0.4-0.1c-1.1-0.2-2.3-0.5-3.4-1c-1.1-0.5-2.2-1.2-3.1-2.1c-1-0.9-1.8-1.9-2.5-3.1c-0.7-1.2-1.1-2.5-1.3-3.8
c-0.1-0.7-0.2-1.4-0.1-2.1c0-0.2,0-0.4,0-0.6c0,0.1,0,0,0,0v-0.1v-0.1c0-0.1,0-0.2,0-0.3c0-0.4,0.1-0.7,0.2-1.1c0.5-3,2-5.9,4.3-8.1
c0.6-0.6,1.2-1.1,1.9-1.5c0.7-0.5,1.4-0.9,2.1-1.2c0.7-0.3,1.5-0.6,2.3-0.8s1.6-0.4,2.4-0.4c0.4,0,0.8-0.1,1.2-0.1
c0.1,0,0.2,0,0.3,0h0.3h0.2c0.1,0,0,0,0,0h0.1h0.3c0.9,0.1,1.8,0.2,2.6,0.4c1.7,0.4,3.4,1,5,1.9c3.2,1.8,5.9,4.5,7.5,7.8
c0.8,1.6,1.4,3.4,1.7,5.3c0.1,0.5,0.1,0.9,0.2,1.4v0.3V66c0,0.1,0,0.2,0,0.3c0,0.1,0,0.2,0,0.3v0.3v0.3c0,0.2,0,0.6,0,0.8
c0,0.5-0.1,1-0.1,1.5c-0.1,0.5-0.1,1-0.2,1.5s-0.2,1-0.3,1.5c-0.2,1-0.6,1.9-0.9,2.9c-0.7,1.9-1.7,3.7-2.9,5.3
c-2.4,3.3-5.7,6-9.4,7.7c-1.9,0.8-3.8,1.5-5.8,1.8c-1,0.2-2,0.3-3,0.3H81h-0.2h-0.3H80h-0.3c0.1,0,0,0,0,0h-0.1
c-0.5,0-1.1,0-1.6-0.1c-2.2-0.2-4.3-0.6-6.4-1.2c-2.1-0.6-4.1-1.4-6-2.4c-3.8-2-7.2-4.9-9.9-8.2c-1.3-1.7-2.5-3.5-3.5-5.4
s-1.7-3.9-2.3-5.9c-0.6-2-0.9-4.1-1-6.2v-0.4v-0.1v-0.1v-0.2V60v-0.1v-0.1v-0.2v-0.5V59l0,0v-0.2c0-0.3,0-0.5,0-0.8
c0-1,0.1-2.1,0.3-3.2c0.1-1.1,0.3-2.1,0.5-3.2c0.2-1.1,0.5-2.1,0.8-3.2c0.6-2.1,1.3-4.1,2.2-6c1.8-3.8,4.1-7.2,6.8-9.9
c0.7-0.7,1.4-1.3,2.2-1.9c0.3-0.3,1-0.9,1.8-1.4c0.8-0.5,1.6-1,2.5-1.4c0.4-0.2,0.8-0.4,1.3-0.6c0.2-0.1,0.4-0.2,0.7-0.3
c0.2-0.1,0.4-0.2,0.7-0.3c0.9-0.4,1.8-0.7,2.7-1c0.2-0.1,0.5-0.1,0.7-0.2c0.2-0.1,0.5-0.1,0.7-0.2c0.5-0.1,0.9-0.2,1.4-0.4
c0.2-0.1,0.5-0.1,0.7-0.2c0.2,0,0.5-0.1,0.7-0.1c0.2,0,0.5-0.1,0.7-0.1l0.4-0.1l0.4-0.1c0.2,0,0.5-0.1,0.7-0.1
c0.3,0,0.5-0.1,0.8-0.1c0.2,0,0.6-0.1,0.8-0.1c0.2,0,0.3,0,0.5-0.1h0.3h0.2h0.2c0.3,0,0.5,0,0.8-0.1h0.4c0,0,0.1,0,0,0h0.1h0.2
c0.2,0,0.5,0,0.7,0c0.9,0,1.8,0,2.7,0c1.8,0.1,3.6,0.3,5.3,0.6c3.4,0.6,6.7,1.7,9.6,3.2c2.9,1.4,5.6,3.2,7.8,5.1
c0.1,0.1,0.3,0.2,0.4,0.4c0.1,0.1,0.3,0.2,0.4,0.4c0.3,0.2,0.5,0.5,0.8,0.7c0.3,0.2,0.5,0.5,0.8,0.7c0.2,0.3,0.5,0.5,0.7,0.8
c1,1,1.9,2.1,2.7,3.1c1.6,2.1,2.9,4.2,3.9,6.2c0.1,0.1,0.1,0.2,0.2,0.4c0.1,0.1,0.1,0.2,0.2,0.4s0.2,0.5,0.4,0.7
c0.1,0.2,0.2,0.5,0.3,0.7c0.1,0.2,0.2,0.5,0.3,0.7c0.4,0.9,0.7,1.8,1,2.7c0.5,1.4,0.8,2.6,1.1,3.6c0.1,0.4,0.5,0.7,0.9,0.7
c0.5,0,0.8-0.4,0.8-0.9C123,52.7,123,51.4,122.9,49.9z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 142.5 145.6" style="enable-background:new 0 0 142.5 145.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#565656;}
.st1{fill:url(#SVGID_1_);}
</style>
<g>
<path class="st0" d="M28.7,131.5c-0.3,7.9-6.6,14.1-14.4,14.1C6.1,145.6,0,139,0,130.9s6.6-14.7,14.7-14.7c3.6,0,7.2,1.6,10.2,4.4
l-2.3,2.9c-2.3-2-5.1-3.4-7.9-3.4c-5.9,0-10.8,4.8-10.8,10.8c0,6.1,4.6,10.8,10.4,10.8c5.2,0,9.3-3.8,10.2-8.8H12.6v-3.5h16.1
V131.5z"/>
<path class="st0" d="M42.3,129.5h-2.2c-2.4,0-4.4,2-4.4,4.4v11.4h-3.9v-19.6H35v1.6c1.1-1.1,2.7-1.6,4.6-1.6h4.2L42.3,129.5z"/>
<path class="st0" d="M63.7,145.3h-3.4v-2.5c-2.6,2.5-6.6,3.7-10.7,1.9c-3-1.3-5.3-4.1-5.9-7.4c-1.2-6.3,3.7-11.9,9.9-11.9
c2.6,0,5,1.1,6.7,2.8v-2.5h3.4V145.3z M59.7,137c0.9-4-2.1-7.6-6-7.6c-3.4,0-6.1,2.8-6.1,6.1c0,3.8,3.3,6.7,7.2,6.1
C57.1,141.2,59.1,139.3,59.7,137z"/>
<path class="st0" d="M71.5,124.7v1.1h6.2v3.4h-6.2v16.1h-3.8v-20.5c0-4.3,3.1-6.8,7-6.8h4.7l-1.6,3.7h-3.1
C72.9,121.6,71.5,123,71.5,124.7z"/>
<path class="st0" d="M98.5,145.3h-3.3v-2.5c-2.6,2.5-6.6,3.7-10.7,1.9c-3-1.3-5.3-4.1-5.9-7.4c-1.2-6.3,3.7-11.9,9.9-11.9
c2.6,0,5,1.1,6.7,2.8v-2.5h3.4v19.6H98.5z M94.5,137c0.9-4-2.1-7.6-6-7.6c-3.4,0-6.1,2.8-6.1,6.1c0,3.8,3.3,6.7,7.2,6.1
C92,141.2,93.9,139.3,94.5,137z"/>
<path class="st0" d="M119.4,133.8v11.5h-3.9v-11.6c0-2.4-2-4.4-4.4-4.4c-2.5,0-4.4,2-4.4,4.4v11.6h-3.9v-19.6h3.2v1.7
c1.4-1.3,3.3-2,5.2-2C115.8,125.5,119.4,129.2,119.4,133.8z"/>
<path class="st0" d="M142.4,145.3h-3.3v-2.5c-2.6,2.5-6.6,3.7-10.7,1.9c-3-1.3-5.3-4.1-5.9-7.4c-1.2-6.3,3.7-11.9,9.9-11.9
c2.6,0,5,1.1,6.7,2.8v-2.5h3.4v19.6H142.4z M138.4,137c0.9-4-2.1-7.6-6-7.6c-3.4,0-6.1,2.8-6.1,6.1c0,3.8,3.3,6.7,7.2,6.1
C135.9,141.2,137.8,139.3,138.4,137z"/>
</g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="71.25" y1="10.4893" x2="71.25" y2="113.3415" gradientTransform="matrix(1 0 0 -1 0 148.6)">
<stop offset="0" style="stop-color:#FCEE1F"/>
<stop offset="1" style="stop-color:#F15B2A"/>
</linearGradient>
<path class="st1" d="M122.9,49.9c-0.2-1.9-0.5-4.1-1.1-6.5c-0.6-2.4-1.6-5-2.9-7.8c-1.4-2.7-3.1-5.6-5.4-8.3
c-0.9-1.1-1.9-2.1-2.9-3.2c1.6-6.3-1.9-11.8-1.9-11.8c-6.1-0.4-9.9,1.9-11.3,2.9c-0.2-0.1-0.5-0.2-0.7-0.3c-1-0.4-2.1-0.8-3.2-1.2
c-1.1-0.3-2.2-0.7-3.3-0.9c-1.1-0.3-2.3-0.5-3.5-0.7c-0.2,0-0.4-0.1-0.6-0.1C83.5,3.6,75.9,0,75.9,0c-8.7,5.6-10.4,13.1-10.4,13.1
s0,0.2-0.1,0.4c-0.5,0.1-0.9,0.3-1.4,0.4c-0.6,0.2-1.3,0.4-1.9,0.7c-0.6,0.3-1.3,0.5-1.9,0.8c-1.3,0.6-2.5,1.2-3.8,1.9
c-1.2,0.7-2.4,1.4-3.5,2.2c-0.2-0.1-0.3-0.2-0.3-0.2c-11.7-4.5-22.1,0.9-22.1,0.9c-0.9,12.5,4.7,20.3,5.8,21.7
c-0.3,0.8-0.5,1.5-0.8,2.3c-0.9,2.8-1.5,5.7-1.9,8.7c-0.1,0.4-0.1,0.9-0.2,1.3c-10.8,5.3-14,16.3-14,16.3c9,10.4,19.6,11,19.6,11
l0,0c1.3,2.4,2.9,4.7,4.6,6.8c0.7,0.9,1.5,1.7,2.3,2.6c-3.3,9.4,0.5,17.3,0.5,17.3c10.1,0.4,16.7-4.4,18.1-5.5c1,0.3,2,0.6,3,0.9
c3.1,0.8,6.3,1.3,9.4,1.4c0.8,0,1.6,0,2.4,0h0.4H80h0.5H81l0,0c4.7,6.8,13.1,7.7,13.1,7.7c5.9-6.3,6.3-12.4,6.3-13.8l0,0
c0,0,0,0,0-0.1s0-0.2,0-0.2l0,0c0-0.1,0-0.2,0-0.3c1.2-0.9,2.4-1.8,3.6-2.8c2.4-2.1,4.4-4.6,6.2-7.2c0.2-0.2,0.3-0.5,0.5-0.7
c6.7,0.4,11.4-4.2,11.4-4.2c-1.1-7-5.1-10.4-5.9-11l0,0c0,0,0,0-0.1-0.1l-0.1-0.1l0,0l-0.1-0.1c0-0.4,0.1-0.8,0.1-1.3
c0.1-0.8,0.1-1.5,0.1-2.3v-0.6v-0.3v-0.1c0-0.2,0-0.1,0-0.2v-0.5v-0.6c0-0.2,0-0.4,0-0.6s0-0.4-0.1-0.6l-0.1-0.6l-0.1-0.6
c-0.1-0.8-0.3-1.5-0.4-2.3c-0.7-3-1.9-5.9-3.4-8.4c-1.6-2.6-3.5-4.8-5.7-6.8c-2.2-1.9-4.6-3.5-7.2-4.6c-2.6-1.2-5.2-1.9-7.9-2.2
c-1.3-0.2-2.7-0.2-4-0.2h-0.5h-0.1h-0.2h-0.2h-0.5c-0.2,0-0.4,0-0.5,0c-0.7,0.1-1.4,0.2-2,0.3c-2.7,0.5-5.2,1.5-7.4,2.8
c-2.2,1.3-4.1,3-5.7,4.9s-2.8,3.9-3.6,6.1c-0.8,2.1-1.3,4.4-1.4,6.5c0,0.5,0,1.1,0,1.6c0,0.1,0,0.3,0,0.4v0.4c0,0.3,0,0.5,0.1,0.8
c0.1,1.1,0.3,2.1,0.6,3.1c0.6,2,1.5,3.8,2.7,5.4s2.5,2.8,4,3.8s3,1.7,4.6,2.2c1.6,0.5,3.1,0.7,4.5,0.6c0.2,0,0.4,0,0.5,0
c0.1,0,0.2,0,0.3,0s0.2,0,0.3,0c0.2,0,0.3,0,0.5,0h0.1h0.1c0.1,0,0.2,0,0.3,0c0.2,0,0.4-0.1,0.5-0.1c0.2,0,0.3-0.1,0.5-0.1
c0.3-0.1,0.7-0.2,1-0.3c0.6-0.2,1.2-0.5,1.8-0.7c0.6-0.3,1.1-0.6,1.5-0.9c0.1-0.1,0.3-0.2,0.4-0.3c0.5-0.4,0.6-1.1,0.2-1.6
c-0.4-0.4-1-0.5-1.5-0.3C88,74,87.9,74,87.7,74.1c-0.4,0.2-0.9,0.4-1.3,0.5c-0.5,0.1-1,0.3-1.5,0.4c-0.3,0-0.5,0.1-0.8,0.1
c-0.1,0-0.3,0-0.4,0c-0.1,0-0.3,0-0.4,0s-0.3,0-0.4,0c-0.2,0-0.3,0-0.5,0c0,0-0.1,0,0,0h-0.1h-0.1c-0.1,0-0.1,0-0.2,0
s-0.3,0-0.4-0.1c-1.1-0.2-2.3-0.5-3.4-1c-1.1-0.5-2.2-1.2-3.1-2.1c-1-0.9-1.8-1.9-2.5-3.1c-0.7-1.2-1.1-2.5-1.3-3.8
c-0.1-0.7-0.2-1.4-0.1-2.1c0-0.2,0-0.4,0-0.6c0,0.1,0,0,0,0v-0.1v-0.1c0-0.1,0-0.2,0-0.3c0-0.4,0.1-0.7,0.2-1.1c0.5-3,2-5.9,4.3-8.1
c0.6-0.6,1.2-1.1,1.9-1.5c0.7-0.5,1.4-0.9,2.1-1.2c0.7-0.3,1.5-0.6,2.3-0.8s1.6-0.4,2.4-0.4c0.4,0,0.8-0.1,1.2-0.1
c0.1,0,0.2,0,0.3,0h0.3h0.2c0.1,0,0,0,0,0h0.1h0.3c0.9,0.1,1.8,0.2,2.6,0.4c1.7,0.4,3.4,1,5,1.9c3.2,1.8,5.9,4.5,7.5,7.8
c0.8,1.6,1.4,3.4,1.7,5.3c0.1,0.5,0.1,0.9,0.2,1.4v0.3V66c0,0.1,0,0.2,0,0.3c0,0.1,0,0.2,0,0.3v0.3v0.3c0,0.2,0,0.6,0,0.8
c0,0.5-0.1,1-0.1,1.5c-0.1,0.5-0.1,1-0.2,1.5s-0.2,1-0.3,1.5c-0.2,1-0.6,1.9-0.9,2.9c-0.7,1.9-1.7,3.7-2.9,5.3
c-2.4,3.3-5.7,6-9.4,7.7c-1.9,0.8-3.8,1.5-5.8,1.8c-1,0.2-2,0.3-3,0.3H81h-0.2h-0.3H80h-0.3c0.1,0,0,0,0,0h-0.1
c-0.5,0-1.1,0-1.6-0.1c-2.2-0.2-4.3-0.6-6.4-1.2c-2.1-0.6-4.1-1.4-6-2.4c-3.8-2-7.2-4.9-9.9-8.2c-1.3-1.7-2.5-3.5-3.5-5.4
s-1.7-3.9-2.3-5.9c-0.6-2-0.9-4.1-1-6.2v-0.4v-0.1v-0.1v-0.2V60v-0.1v-0.1v-0.2v-0.5V59l0,0v-0.2c0-0.3,0-0.5,0-0.8
c0-1,0.1-2.1,0.3-3.2c0.1-1.1,0.3-2.1,0.5-3.2c0.2-1.1,0.5-2.1,0.8-3.2c0.6-2.1,1.3-4.1,2.2-6c1.8-3.8,4.1-7.2,6.8-9.9
c0.7-0.7,1.4-1.3,2.2-1.9c0.3-0.3,1-0.9,1.8-1.4c0.8-0.5,1.6-1,2.5-1.4c0.4-0.2,0.8-0.4,1.3-0.6c0.2-0.1,0.4-0.2,0.7-0.3
c0.2-0.1,0.4-0.2,0.7-0.3c0.9-0.4,1.8-0.7,2.7-1c0.2-0.1,0.5-0.1,0.7-0.2c0.2-0.1,0.5-0.1,0.7-0.2c0.5-0.1,0.9-0.2,1.4-0.4
c0.2-0.1,0.5-0.1,0.7-0.2c0.2,0,0.5-0.1,0.7-0.1c0.2,0,0.5-0.1,0.7-0.1l0.4-0.1l0.4-0.1c0.2,0,0.5-0.1,0.7-0.1
c0.3,0,0.5-0.1,0.8-0.1c0.2,0,0.6-0.1,0.8-0.1c0.2,0,0.3,0,0.5-0.1h0.3h0.2h0.2c0.3,0,0.5,0,0.8-0.1h0.4c0,0,0.1,0,0,0h0.1h0.2
c0.2,0,0.5,0,0.7,0c0.9,0,1.8,0,2.7,0c1.8,0.1,3.6,0.3,5.3,0.6c3.4,0.6,6.7,1.7,9.6,3.2c2.9,1.4,5.6,3.2,7.8,5.1
c0.1,0.1,0.3,0.2,0.4,0.4c0.1,0.1,0.3,0.2,0.4,0.4c0.3,0.2,0.5,0.5,0.8,0.7c0.3,0.2,0.5,0.5,0.8,0.7c0.2,0.3,0.5,0.5,0.7,0.8
c1,1,1.9,2.1,2.7,3.1c1.6,2.1,2.9,4.2,3.9,6.2c0.1,0.1,0.1,0.2,0.2,0.4c0.1,0.1,0.1,0.2,0.2,0.4s0.2,0.5,0.4,0.7
c0.1,0.2,0.2,0.5,0.3,0.7c0.1,0.2,0.2,0.5,0.3,0.7c0.4,0.9,0.7,1.8,1,2.7c0.5,1.4,0.8,2.6,1.1,3.6c0.1,0.4,0.5,0.7,0.9,0.7
c0.5,0,0.8-0.4,0.8-0.9C123,52.7,123,51.4,122.9,49.9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<polygon style="fill:#FFD500;" points="382.395,228.568 291.215,228.568 330.762,10.199 129.603,283.43 220.785,283.43
181.238,501.799 "/>
<g>
<path style="fill:#3D3D3D;" d="M181.234,512c-1.355,0-2.726-0.271-4.033-0.833c-4.357-1.878-6.845-6.514-5.999-11.184
l37.371-206.353h-78.969c-3.846,0-7.367-2.164-9.103-5.597c-1.735-3.433-1.391-7.55,0.889-10.648L322.548,4.153
c2.814-3.822,7.891-5.196,12.25-3.32c4.357,1.878,6.845,6.514,5.999,11.184L303.427,218.37h78.969c3.846,0,7.367,2.164,9.103,5.597
c1.735,3.433,1.391,7.55-0.889,10.648L189.451,507.846C187.481,510.523,184.399,512,181.234,512z M149.777,273.231h71.007
c3.023,0,5.89,1.341,7.828,3.662c1.938,2.32,2.747,5.38,2.208,8.355l-31.704,175.065l163.105-221.545h-71.007
c-3.023,0-5.89-1.341-7.828-3.661c-1.938-2.32-2.747-5.38-2.208-8.355l31.704-175.065L149.777,273.231z"/>
<path style="fill:#3D3D3D;" d="M267.666,171.348c-0.604,0-1.215-0.054-1.829-0.165c-5.543-1.004-9.223-6.31-8.22-11.853l0.923-5.1
c1.003-5.543,6.323-9.225,11.852-8.219c5.543,1.004,9.223,6.31,8.22,11.853l-0.923,5.1
C276.797,167.892,272.503,171.348,267.666,171.348z"/>
<path style="fill:#3D3D3D;" d="M255.455,238.77c-0.604,0-1.215-0.054-1.83-0.165c-5.543-1.004-9.222-6.31-8.218-11.853
l7.037-38.864c1.004-5.543,6.317-9.225,11.854-8.219c5.543,1.004,9.222,6.31,8.219,11.853l-7.037,38.864
C264.587,235.314,260.293,238.77,255.455,238.77z"/>
</g>
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<polygon style="fill:#FFD500;" points="382.395,228.568 291.215,228.568 330.762,10.199 129.603,283.43 220.785,283.43
181.238,501.799 "/>
<g>
<path style="fill:#3D3D3D;" d="M181.234,512c-1.355,0-2.726-0.271-4.033-0.833c-4.357-1.878-6.845-6.514-5.999-11.184
l37.371-206.353h-78.969c-3.846,0-7.367-2.164-9.103-5.597c-1.735-3.433-1.391-7.55,0.889-10.648L322.548,4.153
c2.814-3.822,7.891-5.196,12.25-3.32c4.357,1.878,6.845,6.514,5.999,11.184L303.427,218.37h78.969c3.846,0,7.367,2.164,9.103,5.597
c1.735,3.433,1.391,7.55-0.889,10.648L189.451,507.846C187.481,510.523,184.399,512,181.234,512z M149.777,273.231h71.007
c3.023,0,5.89,1.341,7.828,3.662c1.938,2.32,2.747,5.38,2.208,8.355l-31.704,175.065l163.105-221.545h-71.007
c-3.023,0-5.89-1.341-7.828-3.661c-1.938-2.32-2.747-5.38-2.208-8.355l31.704-175.065L149.777,273.231z"/>
<path style="fill:#3D3D3D;" d="M267.666,171.348c-0.604,0-1.215-0.054-1.829-0.165c-5.543-1.004-9.223-6.31-8.22-11.853l0.923-5.1
c1.003-5.543,6.323-9.225,11.852-8.219c5.543,1.004,9.223,6.31,8.22,11.853l-0.923,5.1
C276.797,167.892,272.503,171.348,267.666,171.348z"/>
<path style="fill:#3D3D3D;" d="M255.455,238.77c-0.604,0-1.215-0.054-1.83-0.165c-5.543-1.004-9.222-6.31-8.218-11.853
l7.037-38.864c1.004-5.543,6.317-9.225,11.854-8.219c5.543,1.004,9.222,6.31,8.219,11.853l-7.037,38.864
C264.587,235.314,260.293,238.77,255.455,238.77z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -10,21 +10,7 @@
// PR for reference: https://github.com/SigNoz/signoz/pull/9694
// -------------------------------------------------------------------------
import '@signozhq/button';
import '@signozhq/calendar';
import '@signozhq/callout';
import '@signozhq/checkbox';
import '@signozhq/combobox';
import '@signozhq/command';
import '@signozhq/design-tokens';
import '@signozhq/dialog';
import '@signozhq/drawer';
import '@signozhq/icons';
import '@signozhq/input';
import '@signozhq/popover';
import '@signozhq/radio-group';
import '@signozhq/resizable';
import '@signozhq/tabs';
import '@signozhq/table';
import '@signozhq/toggle-group';
import '@signozhq/ui';

View File

@@ -80,12 +80,12 @@
mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
transparent 100%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
transparent 100%
);
}

View File

@@ -4,13 +4,14 @@
animation: horizontal-shaking 300ms ease-out;
.error-content {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
border: 1px solid
color-mix(in srgb, var(--danger-background) 20%, transparent);
border-radius: 4px;
&__summary-section {
border-bottom: 1px solid
color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
color-mix(in srgb, var(--danger-background) 20%, transparent);
}
&__summary {
@@ -58,7 +59,7 @@
&__message-badge-line {
background-image: radial-gradient(
circle,
color-mix(in srgb, var(--bg-cherry-500) 30%, transparent) 1px,
color-mix(in srgb, var(--danger-background) 30%, transparent) 1px,
transparent 2px
);
}
@@ -84,7 +85,7 @@
}
&__scroll-hint {
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
}
&__scroll-hint-text {

View File

@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { Button } from '@signozhq/button';
import { Button } from '@signozhq/ui';
import { LifeBuoy } from 'lucide-react';
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
@@ -23,8 +23,10 @@ function AuthHeader(): JSX.Element {
</div>
<Button
className="auth-header-help-button"
prefixIcon={<LifeBuoy size={12} />}
prefix={<LifeBuoy size={12} />}
onClick={handleGetHelp}
variant="solid"
color="none"
>
Get Help
</Button>

View File

@@ -43,12 +43,12 @@
.masked-dots {
mask-image: radial-gradient(
circle at 50% 0%,
color-mix(in srgb, var(--background) 10%, transparent) 0%,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
transparent 56.77%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0%,
color-mix(in srgb, var(--background) 10%, transparent) 0%,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
transparent 56.77%
);
}

View File

@@ -79,7 +79,7 @@ export function useNavigateToExplorer(): (
);
const { getUpdatedQuery } = useUpdatedQuery();
const { selectedDashboard } = useDashboardStore();
const { dashboardData } = useDashboardStore();
const { notifications } = useNotifications();
return useCallback(
@@ -111,7 +111,7 @@ export function useNavigateToExplorer(): (
panelTypes: PANEL_TYPES.TIME_SERIES,
timePreferance: 'GLOBAL_TIME',
},
selectedDashboard,
dashboardData,
})
.then((query) => {
preparedQuery = query;
@@ -140,7 +140,7 @@ export function useNavigateToExplorer(): (
minTime,
maxTime,
getUpdatedQuery,
selectedDashboard,
dashboardData,
notifications,
],
);

View File

@@ -26,14 +26,14 @@
}
}
&--negative {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
.change-percentage-pill {
&__icon {
color: var(--bg-cherry-500);
color: var(--danger-background);
}
&__label {
color: var(--bg-cherry-500);
color: var(--danger-background);
}
}
}

View File

@@ -1,10 +1,13 @@
import { Controller, useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { X } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { toast } from '@signozhq/ui';
import {
Button,
DialogFooter,
DialogWrapper,
Input,
toast,
} from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccounts,
@@ -50,9 +53,7 @@ function CreateServiceAccountModal(): JSX.Element {
} = useCreateServiceAccount({
mutation: {
onSuccess: async () => {
toast.success('Service account created successfully', {
richColors: true,
});
toast.success('Service account created successfully');
reset();
await setIsOpen(null);
await invalidateListServiceAccounts(queryClient);
@@ -128,7 +129,6 @@ function CreateServiceAccountModal(): JSX.Element {
type="button"
variant="solid"
color="secondary"
size="sm"
onClick={handleClose}
>
<X size={12} />
@@ -137,10 +137,10 @@ function CreateServiceAccountModal(): JSX.Element {
<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"
size="sm"
loading={isSubmitting}
disabled={!isValid}
>

View File

@@ -1,7 +1,13 @@
import { toast } from '@signozhq/ui';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import {
render,
screen,
userEvent,
waitFor,
waitForElementToBeRemoved,
} from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
@@ -69,7 +75,6 @@ describe('CreateServiceAccountModal', () => {
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith(
'Service account created successfully',
expect.anything(),
);
});
@@ -121,12 +126,12 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
await screen.findByRole('dialog', { name: /New Service Account/i });
const dialog = await screen.findByRole('dialog', {
name: /New Service Account/i,
});
await user.click(screen.getByRole('button', { name: /Cancel/i }));
expect(
screen.queryByRole('dialog', { name: /New Service Account/i }),
).not.toBeInTheDocument();
await waitForElementToBeRemoved(dialog);
});
it('shows "Name is required" after clearing the name field', async () => {

View File

@@ -1,4 +1,4 @@
import { Calendar } from '@signozhq/calendar';
import { Calendar } from '@signozhq/ui';
import { Button } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';

View File

@@ -7,7 +7,7 @@ import {
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
import { Button } from '@signozhq/button';
import { Button } from '@signozhq/ui';
import { Input, InputRef, Popover, Tooltip } from 'antd';
import cx from 'classnames';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -661,7 +661,9 @@ function CustomTimePicker({
onClick={handleZoomOut}
disabled={zoomOutDisabled}
data-testid="zoom-out-btn"
prefixIcon={<ZoomOut size={14} />}
prefix={<ZoomOut size={14} />}
variant="solid"
color="none"
/>
</Tooltip>
)}

View File

@@ -1,14 +1,8 @@
.download-popover {
.ant-popover-inner {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
var(--l2-background) 0%,
var(--l3-background) 98.68%
);
background-color: var(--l2-background);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
padding: 0 8px 12px 8px;
margin: 6px 0;
}
@@ -19,7 +13,7 @@
.title {
display: flex;
color: var(--l3-foreground);
color: var(--l1-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
@@ -38,7 +32,7 @@
flex-direction: column;
:global(.ant-radio-wrapper) {
color: var(--foreground);
color: var(--l1-foreground);
font-family: Inter;
font-size: 13px;
}

View File

@@ -1,6 +1,5 @@
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { Trash2, X } from '@signozhq/icons';
import { Button, DialogWrapper } from '@signozhq/ui';
import { MemberRow } from 'components/MembersTable/MembersTable';
interface DeleteMemberDialogProps {
@@ -36,6 +35,24 @@ function DeleteMemberDialog({
</>
);
const footer = (
<>
<Button variant="solid" color="secondary" onClick={onClose}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
disabled={isDeleting}
onClick={onConfirm}
>
<Trash2 size={12} />
{isDeleting ? 'Processing...' : title}
</Button>
</>
);
return (
<DialogWrapper
open={open}
@@ -49,25 +66,9 @@ function DeleteMemberDialog({
className="alert-dialog delete-dialog"
showCloseButton={false}
disableOutsideClick={false}
footer={footer}
>
<p className="delete-dialog__body">{body}</p>
<DialogFooter className="delete-dialog__footer">
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
size="sm"
disabled={isDeleting}
onClick={onConfirm}
>
<Trash2 size={12} />
{isDeleting ? 'Processing...' : title}
</Button>
</DialogFooter>
{body}
</DialogWrapper>
);
}

View File

@@ -2,7 +2,7 @@
&__layout {
display: flex;
flex-direction: column;
height: calc(100vh - 48px);
height: 100%;
}
&__body {
@@ -11,7 +11,6 @@
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--padding-5) var(--padding-4);
}
&__field {
@@ -50,6 +49,7 @@
border-radius: 2px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
box-sizing: border-box;
&--disabled {
cursor: not-allowed;
@@ -120,17 +120,11 @@
align-items: center;
justify-content: space-between;
width: 100%;
height: 56px;
padding: 0 var(--padding-4);
border-top: 1px solid var(--l1-border);
flex-shrink: 0;
background: var(--card);
}
&__footer-left {
display: flex;
align-items: center;
gap: var(--spacing-8);
}
&__footer-right {
@@ -223,10 +217,6 @@
color: var(--l1-foreground);
}
[data-slot='dialog-description'] {
width: 510px;
}
&__content {
display: flex;
flex-direction: column;

View File

@@ -1,10 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Button } from '@signozhq/button';
import { DrawerWrapper } from '@signozhq/drawer';
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { Badge, toast } from '@signozhq/ui';
import { Badge, Button, DrawerWrapper, Input, toast } from '@signozhq/ui';
import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
@@ -207,7 +204,7 @@ function EditMemberDrawer({
onSuccess: (): void => {
toast.success(
isInvited ? 'Invite revoked successfully' : 'Member deleted successfully',
{ richColors: true, position: 'top-right' },
{ position: 'top-right' },
);
setShowDeleteConfirm(false);
onComplete();
@@ -342,10 +339,7 @@ function EditMemberDrawer({
if (errors.length > 0) {
setSaveErrors(errors);
} else {
toast.success('Member details updated successfully', {
richColors: true,
position: 'top-right',
});
toast.success('Member details updated successfully');
onComplete();
}
@@ -403,7 +397,6 @@ function EditMemberDrawer({
onClose();
} else {
toast.error('Failed to generate password reset link', {
richColors: true,
position: 'top-right',
});
}
@@ -427,15 +420,12 @@ function EditMemberDrawer({
linkType === 'invite'
? 'Invite link copied to clipboard'
: 'Reset link copied to clipboard';
toast.success(message, { richColors: true, position: 'top-right' });
toast.success(message);
}, [resetLink, copyToClipboard, linkType]);
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy link', {
richColors: true,
position: 'top-right',
});
toast.error('Failed to copy link');
}
}, [copyState.error]);
@@ -596,16 +586,21 @@ function EditMemberDrawer({
const drawerContent = (
<div className="edit-member-drawer__layout">
<div className="edit-member-drawer__body">{drawerBody}</div>
</div>
);
const footer = (
<div className="edit-member-drawer__footer">
{!isDeleted && (
<div className="edit-member-drawer__footer">
<>
<div className="edit-member-drawer__footer-left">
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
<span className="edit-member-drawer__tooltip-wrapper">
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--danger"
onClick={(): void => setShowDeleteConfirm(true)}
disabled={isRootUser || isSelf}
variant="link"
color="destructive"
>
<Trash2 size={12} />
{isInvited ? 'Revoke Invite' : 'Delete Member'}
@@ -617,9 +612,10 @@ function EditMemberDrawer({
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
<span className="edit-member-drawer__tooltip-wrapper">
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
onClick={handleGenerateResetLink}
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
variant="link"
color="warning"
>
<RefreshCw size={12} />
{isGeneratingLink
@@ -638,7 +634,7 @@ function EditMemberDrawer({
</div>
<div className="edit-member-drawer__footer-right">
<Button variant="solid" color="secondary" size="sm" onClick={handleClose}>
<Button variant="solid" color="secondary" onClick={handleClose}>
<X size={14} />
Cancel
</Button>
@@ -646,14 +642,13 @@ function EditMemberDrawer({
<Button
variant="solid"
color="primary"
size="sm"
disabled={!isDirty || isSaving || isRootUser}
onClick={handleSave}
>
{isSaving ? 'Saving...' : 'Save Member Details'}
</Button>
</div>
</div>
</>
)}
</div>
);
@@ -668,14 +663,14 @@ function EditMemberDrawer({
}
}}
direction="right"
type="panel"
showCloseButton
showOverlay={false}
allowOutsideClick
header={{ title: 'Member Details' }}
content={drawerContent}
className="edit-member-drawer"
/>
title="Member Details"
footer={footer}
width="wide"
>
{drawerContent}
</DrawerWrapper>
<ResetLinkDialog
open={showResetLinkDialog}

View File

@@ -1,6 +1,5 @@
import { Button } from '@signozhq/button';
import { DialogWrapper } from '@signozhq/dialog';
import { Check, Copy } from '@signozhq/icons';
import { Button, DialogWrapper } from '@signozhq/ui';
interface ResetLinkDialogProps {
open: boolean;
@@ -49,7 +48,7 @@ function ResetLinkDialog({
color="secondary"
size="sm"
onClick={onCopy}
prefixIcon={hasCopied ? <Check size={12} /> : <Copy size={12} />}
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
className="reset-link-dialog__copy-btn"
>
{hasCopied ? 'Copied!' : 'Copy'}

View File

@@ -20,36 +20,6 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
jest.mock('@signozhq/drawer', () => ({
DrawerWrapper: ({
content,
open,
}: {
content?: ReactNode;
open: boolean;
}): JSX.Element | null => (open ? <div>{content}</div> : null),
}));
jest.mock('@signozhq/dialog', () => ({
DialogWrapper: ({
children,
open,
title,
}: {
children?: ReactNode;
open: boolean;
title?: string;
}): JSX.Element | null =>
open ? (
<div role="dialog" aria-label={title}>
{children}
</div>
) : null,
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
<div>{children}</div>
),
}));
jest.mock('api/generated/services/users', () => ({
useDeleteUser: jest.fn(),
useGetUser: jest.fn(),
@@ -66,6 +36,41 @@ jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
DrawerWrapper: ({
children,
footer,
open,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
}): JSX.Element | null =>
open ? (
<div>
{children}
{footer}
</div>
) : null,
DialogWrapper: ({
children,
footer,
open,
title,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
title?: string;
}): JSX.Element | null =>
open ? (
<div role="dialog" aria-label={title}>
{children}
{footer}
</div>
) : null,
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
<div>{children}</div>
),
toast: {
success: jest.fn(),
error: jest.fn(),
@@ -160,6 +165,8 @@ function renderDrawer(
describe('EditMemberDrawer', () => {
beforeEach(() => {
jest.clearAllMocks();
mockCopyState.value = undefined;
mockCopyState.error = undefined;
showErrorModal.mockClear();
server.use(
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
@@ -726,16 +733,16 @@ describe('EditMemberDrawer', () => {
await user.click(screen.getByRole('button', { name: /^copy$/i }));
await waitFor(() => {
expect(mockCopyToClipboard).toHaveBeenCalledWith(
expect.stringContaining('reset-tok-abc'),
);
expect(
screen.getByRole('button', { name: /copied!/i }),
).toBeInTheDocument();
expect(mockToast.success).toHaveBeenCalledWith(
'Reset link copied to clipboard',
expect.anything(),
);
});
expect(mockCopyToClipboard).toHaveBeenCalledWith(
expect.stringContaining('reset-tok-abc'),
);
expect(screen.getByRole('button', { name: /copied!/i })).toBeInTheDocument();
});
});
});

View File

@@ -5,12 +5,12 @@
align-items: center;
gap: 4px;
border-radius: 20px;
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
padding-left: 3px;
padding-right: 8px;
cursor: pointer;
span {
color: var(--bg-cherry-500);
color: var(--danger-background);
font-size: 10px;
font-weight: 500;
line-height: 20px; /* 200% */
@@ -21,7 +21,7 @@
&__wrap {
background: linear-gradient(
180deg,
color-mix(in srgb, var(--background) 12%, transparent) 0.07%,
color-mix(in srgb, var(--l1-background) 12%, transparent) 0.07%,
color-mix(in srgb, var(--bg-sakura-950) 24%, transparent) 50.04%,
color-mix(in srgb, var(--bg-sakura-800) 36%, transparent) 75.02%,
color-mix(in srgb, var(--bg-sakura-600) 48%, transparent) 87.51%,
@@ -40,15 +40,17 @@
margin: auto;
}
}
&__body {
padding: 0;
background: var(--l2-background);
overflow: hidden;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
&__header {
background: none !important;
.ant-modal-title {
display: flex;
justify-content: space-between;
@@ -80,6 +82,7 @@
pointer-events: none;
}
}
.close-button {
padding: 3px 7px;
background: var(--l2-background);
@@ -90,15 +93,15 @@
box-shadow: none;
}
}
&__footer {
margin: 0 !important;
height: 6px;
background: var(--bg-sakura-500);
}
&__content {
padding: 0 !important;
border-radius: 4px;
overflow: hidden;
background: none !important;
}
}

View File

@@ -5,7 +5,6 @@
&__summary-section {
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--l1-border);
}
&__summary {

View File

@@ -5,7 +5,6 @@
border-radius: 2px 0px 0px 2px;
.label {
color: var(--l2-foreground);
font-size: 12px;
font-style: normal;
font-weight: 500;
@@ -21,8 +20,9 @@
padding: 0px 8px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--l2-border);
background: var(--l2-background);
color: var(--l2-foreground);
display: flex;
justify-content: flex-start;
@@ -37,6 +37,7 @@
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
border-right: none;
border-left: none;
@@ -46,6 +47,7 @@
border-bottom-left-radius: 0px;
font-size: 12px !important;
line-height: 27px;
&::placeholder {
color: var(--l2-foreground) !important;
font-size: 12px !important;
@@ -61,8 +63,8 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--l2-border);
background: var(--l2-background);
height: 38px;
width: 38px;
}
@@ -71,7 +73,7 @@
.input {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
background: var(--l2-background);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}

View File

@@ -181,7 +181,7 @@
box-shadow: none;
&:hover {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
opacity: 0.9;
}
}
@@ -196,12 +196,16 @@
}
.invite-team-members-error-callout {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--danger-background) 20%, transparent);
border-radius: 4px;
animation: horizontal-shaking 300ms ease-out;
}
.invite-members-modal__error-callout {
display: flex;
}
@keyframes horizontal-shaking {
0% {
transform: translateX(0);

View File

@@ -1,11 +1,14 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Button } from '@signozhq/button';
import { Callout } from '@signozhq/callout';
import { Style } from '@signozhq/design-tokens';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { ChevronDown, CircleAlert, Plus, Trash2, X } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { toast } from '@signozhq/ui';
import {
Button,
Callout,
DialogFooter,
DialogWrapper,
Input,
toast,
} from '@signozhq/ui';
import { Select } from 'antd';
import inviteUsers from 'api/v1/invite/bulk/create';
import sendInvite from 'api/v1/invite/create';
@@ -200,10 +203,7 @@ function InviteMembersModal({
})),
});
}
toast.success('Invites sent successfully', {
richColors: true,
position: 'top-right',
});
toast.success('Invites sent successfully', { position: 'top-right' });
resetAndClose();
onComplete?.();
} catch (err) {
@@ -274,7 +274,6 @@ function InviteMembersModal({
<Button
variant="ghost"
color="destructive"
className="remove-team-member-button"
onClick={(): void => removeRow(row.id)}
aria-label="Remove row"
>
@@ -289,14 +288,16 @@ function InviteMembersModal({
</div>
{(hasInvalidEmails || hasInvalidRoles) && (
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className="invite-team-members-error-callout"
description={getValidationErrorMessage()}
/>
<div className="invite-members-modal__error-callout">
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
>
{getValidationErrorMessage()}
</Callout>
</div>
)}
</div>
@@ -304,9 +305,8 @@ function InviteMembersModal({
<Button
variant="dashed"
color="secondary"
size="sm"
className="add-another-member-button"
prefixIcon={<Plus size={12} color={Style.L1_FOREGROUND} />}
prefix={<Plus size={12} color={Style.L1_FOREGROUND} />}
onClick={addRow}
>
Add another
@@ -317,7 +317,6 @@ function InviteMembersModal({
type="button"
variant="solid"
color="secondary"
size="sm"
onClick={resetAndClose}
>
<X size={12} />
@@ -327,7 +326,6 @@ function InviteMembersModal({
<Button
variant="solid"
color="primary"
size="sm"
onClick={handleSubmit}
disabled={isSubmitDisabled}
loading={isSubmitting}

View File

@@ -110,7 +110,7 @@
}
&.ERROR {
border-color: var(--bg-cherry-500);
border-color: var(--danger-background);
}
}

View File

@@ -3,7 +3,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly
import { useCopyToClipboard, useLocation } from 'react-use';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
import { Button } from '@signozhq/ui';
import { Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
import type { RadioChangeEvent } from 'antd/lib';
import cx from 'classnames';
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
@@ -363,7 +364,9 @@ function LogDetailInner({
mouseLeaveDelay={0}
>
<Button
icon={<ChevronUp size={14} />}
variant="outlined"
color="secondary"
prefix={<ChevronUp size={14} />}
className="log-arrow-btn log-arrow-btn-up"
disabled={isPrevDisabled}
onClick={(): void => handleNavigateLog({ direction: 'previous' })}
@@ -375,7 +378,9 @@ function LogDetailInner({
mouseLeaveDelay={0}
>
<Button
icon={<ChevronDown size={14} />}
variant="outlined"
color="secondary"
prefix={<ChevronDown size={14} />}
className="log-arrow-btn log-arrow-btn-down"
disabled={isNextDisabled}
onClick={(): void => handleNavigateLog({ direction: 'next' })}
@@ -385,8 +390,10 @@ function LogDetailInner({
{showOpenInExplorerBtn && (
<div>
<Button
variant="outlined"
color="secondary"
prefix={<Compass size={16} />}
className="open-in-explorer-btn"
icon={<Compass size={16} />}
onClick={handleOpenInExplorer}
>
Open in Explorer
@@ -482,8 +489,10 @@ function LogDetailInner({
mouseLeaveDelay={0}
>
<Button
className="action-btn"
icon={<Filter size={16} />}
variant="link"
color="secondary"
size="sm"
prefix={<Filter size={12} />}
onClick={handleFilterVisible}
/>
</Tooltip>
@@ -498,8 +507,10 @@ function LogDetailInner({
mouseLeaveDelay={0}
>
<Button
className="action-btn"
icon={<Copy size={16} />}
variant="link"
color="secondary"
size="sm"
prefix={<Copy size={12} />}
onClick={selectedView === VIEW_TYPES.JSON ? handleJSONCopy : onLogCopy}
/>
</Tooltip>

View File

@@ -1,6 +1,4 @@
.log-state-indicator {
padding-left: 8px;
.line {
margin: 0 8px;
min-height: 24px;
@@ -94,7 +92,7 @@
background-color: var(--bg-cherry-600);
}
&.severity-error-1 {
background-color: var(--bg-cherry-500);
background-color: var(--danger-background);
}
&.severity-error-2 {
background-color: var(--bg-cherry-400);

View File

@@ -0,0 +1,43 @@
import { Color } from '@signozhq/design-tokens';
import { LogType } from './LogStateIndicator';
export function getRowBackgroundColor(
isDarkMode: boolean,
logType?: string,
): string {
if (isDarkMode) {
switch (logType) {
case LogType.INFO:
return `${Color.BG_ROBIN_500}40`;
case LogType.WARN:
return `${Color.BG_AMBER_500}40`;
case LogType.ERROR:
return `${Color.BG_CHERRY_500}40`;
case LogType.TRACE:
return `${Color.BG_FOREST_400}40`;
case LogType.DEBUG:
return `${Color.BG_AQUA_500}40`;
case LogType.FATAL:
return `${Color.BG_SAKURA_500}40`;
default:
return `${Color.BG_ROBIN_500}40`;
}
}
switch (logType) {
case LogType.INFO:
return Color.BG_ROBIN_100;
case LogType.WARN:
return Color.BG_AMBER_100;
case LogType.ERROR:
return Color.BG_CHERRY_100;
case LogType.TRACE:
return Color.BG_FOREST_200;
case LogType.DEBUG:
return Color.BG_AQUA_100;
case LogType.FATAL:
return Color.BG_SAKURA_100;
default:
return Color.BG_VANILLA_300;
}
}

View File

@@ -1,4 +1,5 @@
import { CSSProperties } from 'react';
import { Color } from '@signozhq/design-tokens';
import { TableProps } from 'antd';
export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
@@ -7,7 +8,7 @@ export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
paddingBottom: 6,
paddingRight: 8,
paddingLeft: 8,
color: isDarkMode ? 'var(--bg-vanilla-400)' : 'var(--bg-slate-400)',
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_400,
fontSize: '14px',
fontStyle: 'normal',
fontWeight: 400,

View File

@@ -1,3 +1,4 @@
import { Color } from '@signozhq/design-tokens';
import { FontSize } from 'container/OptionsMenu/types';
import styled from 'styled-components';
@@ -10,7 +11,7 @@ interface TableBodyContentProps {
export const TableBodyContent = styled.div<TableBodyContentProps>`
margin-bottom: 0;
color: ${(props): string =>
props.isDarkMode ? 'var(--bg-vanilla-400, #c0c1c3)' : 'var(--bg-slate-400)'};
props.isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_400};
font-size: 14px;
font-style: normal;
font-weight: 400;

View File

@@ -0,0 +1,114 @@
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 { FontSize } from 'container/OptionsMenu/types';
import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import type { TableColumnDef } from '../../TanStackTableView/types';
import LogStateIndicator from '../LogStateIndicator/LogStateIndicator';
type UseLogsTableColumnsProps = {
fields: IField[];
fontSize: FontSize;
appendTo?: 'center' | 'end';
};
export function useLogsTableColumns({
fields,
fontSize,
appendTo = 'center',
}: UseLogsTableColumnsProps): TableColumnDef<ILog>[] {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
return useMemo<TableColumnDef<ILog>[]>(() => {
const stateIndicatorCol: TableColumnDef<ILog> = {
id: 'state-indicator',
header: '',
pin: 'left',
enableMove: false,
enableResize: false,
enableRemove: false,
canBeHidden: false,
width: { fixed: 24 },
cell: ({ row }): ReactElement => (
<LogStateIndicator
fontSize={fontSize}
severityText={row.severity_text as string}
severityNumber={row.severity_number as number}
/>
),
};
const fieldColumns: TableColumnDef<ILog>[] = fields
.filter((f): boolean => !['id', 'body', 'timestamp'].includes(f.name))
.map(
(f): TableColumnDef<ILog> => ({
id: f.name,
header: f.name,
accessorFn: (log): unknown => FlatLogData(log)[f.name],
enableRemove: true,
width: { min: 192 },
cell: ({ value }): ReactElement => (
<TanStackTable.Text>{String(value ?? '')}</TanStackTable.Text>
),
}),
);
const timestampCol: TableColumnDef<ILog> | null = fields.some(
(f) => f.name === 'timestamp',
)
? {
id: 'timestamp',
header: 'Timestamp',
accessorFn: (log): unknown => log.timestamp,
width: { default: 170, min: 170 },
cell: ({ value }): ReactElement => {
const ts = value as string | number;
const formatted =
typeof ts === 'string'
? formatTimezoneAdjustedTimestamp(ts, DATE_TIME_FORMATS.ISO_DATETIME_MS)
: formatTimezoneAdjustedTimestamp(
ts / 1e6,
DATE_TIME_FORMATS.ISO_DATETIME_MS,
);
return <TanStackTable.Text>{formatted}</TanStackTable.Text>;
},
}
: null;
const bodyCol: TableColumnDef<ILog> | null = fields.some(
(f) => f.name === 'body',
)
? {
id: 'body',
header: 'Body',
accessorFn: (log): string => log.body,
canBeHidden: false,
width: { default: '100%', min: 300 },
cell: ({ value, isActive }): ReactElement => (
<TanStackTable.Text
dangerouslySetInnerHTML={{
__html: getSanitizedLogBody(value as string, {
shouldEscapeHtml: true,
}),
}}
data-active={isActive}
/>
),
}
: null;
return [
stateIndicatorCol,
...(timestampCol ? [timestampCol] : []),
...(appendTo === 'center' ? fieldColumns : []),
...(bodyCol ? [bodyCol] : []),
...(appendTo === 'end' ? fieldColumns : []),
];
}, [fields, appendTo, fontSize, formatTimezoneAdjustedTimestamp]);
}

View File

@@ -33,8 +33,9 @@
display: flex;
align-items: center;
p {
margin-bottom: 0;
.timestamp-text {
color: var(--l1-foreground);
margin: 0 !important;
}
}

View File

@@ -123,7 +123,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
return {
children: (
<div className="table-timestamp">
<p className={cx('text', fontSize)}>{date}</p>
<p className={cx('timestamp-text text', fontSize)}>{date}</p>
</div>
),
};

View File

@@ -35,7 +35,7 @@
}
.text {
color: var(--muted-foreground);
color: var(--l1-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
@@ -93,7 +93,7 @@
gap: 12px;
.title {
color: var(--muted-foreground);
color: var(--l1-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
@@ -139,7 +139,8 @@
line-height: 18px;
letter-spacing: 0.08em;
text-align: left;
color: var(--muted-foreground);
color: var(--l1-foreground);
}
.menu-items {
@@ -177,7 +178,7 @@
padding: 12px;
.title {
color: var(--muted-foreground);
color: var(--l1-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
@@ -330,7 +331,7 @@
}
.title {
color: var(--muted-foreground);
color: var(--l1-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
@@ -486,169 +487,3 @@
}
}
}
.lightMode {
.format-options-popover {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
rgba(255, 255, 255, 0.8) 0%,
rgba(255, 255, 255, 0.9) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
.nested-menu-container {
.font-size-dropdown {
.back-btn {
.text {
color: var(--l2-background);
}
}
.content {
.option-btn {
.text {
color: var(--l2-background);
}
.text:hover {
color: var(--l3-background);
}
}
}
}
.add-new-column-container {
.add-new-column-header {
.title {
color: var(--l2-foreground);
}
}
.add-new-column-content {
.column-format-new-options {
.column-name {
color: var(--l2-background);
&.selected {
background-color: var(--l3-background);
}
}
}
.loading-container {
color: var(--l2-background);
}
}
}
.font-size-container {
.title {
color: var(--l2-foreground);
}
.value {
.font-value {
color: var(--l2-background);
}
}
}
.horizontal-line {
background: var(--l3-background);
}
.item-content {
.column-divider {
border-top: 2px solid var(--l1-border);
}
}
.max-lines-per-row {
.title {
color: var(--l2-foreground);
.lucide {
color: var(--l1-foreground);
}
}
.max-lines-per-row-input {
border: 1px solid var(--l1-border);
.periscope-btn {
background: var(--l3-background);
}
}
}
.menu-container {
.title {
color: var(--l2-foreground);
}
.item {
.item-label {
color: var(--l2-background);
}
}
}
.selected-item-content-container {
.title {
color: var(--l2-foreground);
.lucide {
color: var(--l1-foreground);
}
}
.horizontal-line {
background: var(--l3-background);
}
.item-content {
.max-lines-per-row-input {
border: 1px solid var(--l1-border);
.periscope-btn {
background: var(--l3-background);
}
}
.column-format,
.column-format-new-options {
.column-name {
color: var(--l1-foreground);
}
}
}
}
&.active {
.nested-menu-container {
backdrop-filter: blur(18px);
.item {
.item-label {
color: var(--l1-foreground);
}
}
}
.selected-item-content-container {
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
rgba(255, 255, 255, 0.8) 0%,
rgba(255, 255, 255, 0.9) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
}
}
}
}
}
}

View File

@@ -21,7 +21,7 @@
.ant-table-thead {
> tr > th,
> tr > td {
background: var(--background);
background: var(--l1-background);
font-size: var(--paragraph-small-600-font-size);
font-weight: var(--paragraph-small-600-font-weight);
line-height: var(--paragraph-small-600-line-height);

View File

@@ -1445,11 +1445,22 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
// Custom dropdown render with sections support
const customDropdownRender = useCallback((): React.ReactElement => {
// Process options based on current search
const processedOptions =
selectedValues.length > 0 && isEmpty(searchText)
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
: filteredOptions;
// When ALL is selected and the options contain sections (groups),
// skip prioritization so section headers (e.g. "Related values" /
// "All values") remain visible instead of being collapsed away by
// every option getting hoisted to the top. For flat option lists we
// still prioritize so selected/synthesized values stay rendered.
const hasSections = filteredOptions.some(
(opt) => 'options' in opt && Array.isArray(opt.options),
);
const shouldPrioritize =
selectedValues.length > 0 &&
isEmpty(searchText) &&
!(hasSections && (allOptionShown || isAllSelected));
const processedOptions = shouldPrioritize
? prioritizeOrAddOptionForMultiSelect(filteredOptions, selectedValues)
: filteredOptions;
const { sectionOptions, nonSectionOptions } = splitOptions(processedOptions);
@@ -1747,6 +1758,8 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}, [
selectedValues,
searchText,
allOptionShown,
isAllSelected,
filteredOptions,
splitOptions,
isLabelPresent,

View File

@@ -6,6 +6,7 @@ import {
screen,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CustomMultiSelect from '../CustomMultiSelect';
@@ -283,4 +284,68 @@ describe('CustomMultiSelect Component', () => {
// When all options are selected, component shows ALL tag instead
expect(screen.getByText('ALL')).toBeInTheDocument();
});
describe('section visibility when ALL is selected', () => {
it('keeps group headers visible when every grouped value is selected', async () => {
const user = userEvent.setup();
renderWithVirtuoso(
<CustomMultiSelect
options={mockGroupedOptions}
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
/>,
);
await user.click(screen.getByRole('combobox'));
await waitFor(() => {
expect(screen.getByText('Group 1')).toBeInTheDocument();
expect(screen.getByText('Group 2')).toBeInTheDocument();
});
});
it('keeps every grouped option visible within its section when all are selected', async () => {
const user = userEvent.setup();
renderWithVirtuoso(
<CustomMultiSelect
options={mockGroupedOptions}
value={['g1-option1', 'g1-option2', 'g2-option1', 'g2-option2']}
/>,
);
await user.click(screen.getByRole('combobox'));
await waitFor(() => {
const group1Region = screen.getByRole('group', {
name: 'Group 1 options',
});
const group2Region = screen.getByRole('group', {
name: 'Group 2 options',
});
// Each option stays inside its original section rather than being
// hoisted into a flat selected-first list.
expect(group1Region).toHaveTextContent('Group 1 - Option 1');
expect(group1Region).toHaveTextContent('Group 1 - Option 2');
expect(group2Region).toHaveTextContent('Group 2 - Option 1');
expect(group2Region).toHaveTextContent('Group 2 - Option 2');
});
});
it('keeps group headers visible when value is the ALL sentinel', async () => {
const user = userEvent.setup();
renderWithVirtuoso(
<CustomMultiSelect
options={mockGroupedOptions}
value={('__ALL__' as unknown) as string[]}
/>,
);
await user.click(screen.getByRole('combobox'));
await waitFor(() => {
expect(screen.getByText('Group 1')).toBeInTheDocument();
expect(screen.getByText('Group 2')).toBeInTheDocument();
});
});
});
});

View File

@@ -355,7 +355,7 @@ $custom-border-color: #2c3044;
.navigation-error {
.navigation-text,
.navigation-icons {
color: var(--bg-cherry-500) !important;
color: var(--danger-background) !important;
}
display: flex;
align-items: center;

View File

@@ -1,3 +1,5 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.query-builder-v2 {
display: flex;
flex-direction: row;
@@ -274,7 +276,7 @@
.ant-input-group-addon {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
background: var(--l3-background);
background: var(--l2-background);
color: var(--l2-foreground);
font-size: 12px;
font-weight: 300;

View File

@@ -50,8 +50,8 @@ const havingOperators = [
value: 'IN',
},
{
label: 'NOT_IN',
value: 'NOT_IN',
label: 'NOT IN',
value: 'NOT IN',
},
];
@@ -129,7 +129,7 @@ function HavingFilter({
const operator = havingOperators[j];
newOptions.push({
label: `${opt.func}(${opt.arg}) ${operator.label}`,
value: `${opt.func}(${opt.arg}) ${operator.label} `,
value: `${opt.func}(${opt.arg}) ${operator.value} `,
apply: (
view: EditorView,
completion: { label: string; value: string },

View File

@@ -1,3 +1,5 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.query-add-ons {
width: 100%;
}
@@ -102,7 +104,7 @@
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
padding: 0px !important;
background-color: var(--card) !important;
background-color: var(--l2-background) !important;
&:focus-within {
border-color: var(--l1-border);
@@ -211,7 +213,7 @@
.cm-line {
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--card) !important;
background-color: var(--l2-background) !important;
::-moz-selection {
background: var(--l3-background) !important;
@@ -249,8 +251,8 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--l2-border);
background: var(--l2-background);
height: 38px;
width: 38px;
@@ -284,108 +286,3 @@
}
}
}
.lightMode {
.add-ons-list {
.add-ons-tabs {
.add-on-tab-title {
color: var(--l1-foreground) !important;
}
.tab {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
&:first-child {
border-left: 1px solid var(--l1-border) !important;
}
}
.tab::before {
background: var(--l3-background) !important;
}
.selected-view {
color: var(--primary-background) !important;
border: 1px solid var(--l1-border) !important;
}
.selected-view::before {
background: var(--l3-background) !important;
}
}
.compass-button {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
.having-filter-container {
.having-filter-select-container {
.having-filter-select-editor {
.cm-editor {
&:focus-within {
border-color: var(--l1-border) !important;
}
.cm-content {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
&:focus-within {
border-color: var(--l1-border) !important;
}
}
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border) !important;
color: var(--l1-foreground) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
ul {
li {
color: var(--l1-foreground) !important;
&:hover {
background: var(--l3-background) !important;
}
&[aria-selected='true'] {
background: var(--l3-background) !important;
font-weight: 600 !important;
}
}
}
}
.cm-line {
background-color: var(--l1-background) !important;
::-moz-selection {
background: var(--l1-background) !important;
}
::selection {
background: var(--l3-background) !important;
}
.chip-decorator {
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
}
}
.cm-selectionBackground {
background: var(--l1-background) !important;
}
}
}
.close-btn {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
}
}

View File

@@ -1,3 +1,5 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.query-aggregation-container {
display: block;
@@ -26,7 +28,7 @@
&.error {
.cm-editor {
.cm-content {
border-color: var(--bg-cherry-500) !important;
border-color: var(--danger-background) !important;
}
}
}
@@ -140,7 +142,7 @@
.cm-line {
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l1-background) !important;
background-color: var(--l2-background) !important;
::-moz-selection {
background: var(--l3-background) !important;
@@ -184,7 +186,7 @@
max-width: 300px;
.query-aggregation-error-message {
color: var(--bg-cherry-500);
color: var(--danger-background);
font-size: 12px;
line-height: 16px;
}
@@ -196,6 +198,7 @@
min-width: auto;
}
}
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
@@ -270,7 +273,7 @@
.cm-line {
::-moz-selection {
background: var(--l1-background) !important;
background: var(--l2-background) !important;
opacity: 0.5 !important;
}

View File

@@ -1,3 +1,5 @@
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
.code-mirror-where-clause {
width: 100%;
display: flex;
@@ -30,7 +32,7 @@
border-left: none !important;
&.hasErrors {
border-color: var(--bg-cherry-500);
border-color: var(--danger-background);
}
}
}
@@ -39,7 +41,7 @@
&.hasErrors {
.cm-editor {
.cm-content {
border-color: var(--bg-cherry-500);
border-color: var(--danger-background);
border-top-right-radius: 0px !important;
border-bottom-right-radius: 0px !important;
}
@@ -156,7 +158,7 @@
.cm-line {
line-height: 34px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l1-background) !important;
background-color: var(--l2-background) !important;
::-moz-selection {
background: var(--l3-background) !important;
@@ -211,7 +213,7 @@
.invalid {
background-color: color-mix(in srgb, var(--bg-cherry-400) 10%, transparent);
color: var(--bg-cherry-500);
color: var(--danger-background);
}
.query-validation-status {
@@ -232,7 +234,7 @@
font-size: 12px;
font-family: 'Space Mono', monospace !important;
color: var(--bg-cherry-500);
color: var(--danger-background);
padding: 8px;
}
}
@@ -454,30 +456,3 @@
margin-top: -6px !important;
}
}
.lightMode {
.code-mirror-where-clause {
.cm-editor {
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border);
backdrop-filter: blur(20px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
}
.cm-line {
::-moz-selection {
background: var(--bg-robin-200) !important;
}
::selection {
background: var(--bg-robin-200) !important;
}
}
.cm-selectionBackground {
background: var(--bg-robin-200) !important;
}
}
}
}

View File

@@ -87,8 +87,8 @@ jest.mock('hooks/useDarkMode', () => ({
}));
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
useDashboardStore: (): { selectedDashboard: undefined } => ({
selectedDashboard: undefined,
useDashboardStore: (): { dashboardData: undefined } => ({
dashboardData: undefined,
}),
}));

View File

@@ -177,7 +177,7 @@
width: 2px;
height: 11px;
border-radius: 2px;
background: var(--bg-cherry-500);
background: var(--danger-background);
}
.label-true {

View File

@@ -158,12 +158,12 @@
mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
transparent 100%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
transparent 100%
);
}

View File

@@ -11,7 +11,7 @@ import {
ComboboxItem,
ComboboxList,
ComboboxTrigger,
} from '@signozhq/combobox';
} from '@signozhq/ui';
import { Skeleton, Switch, Tooltip, Typography } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
@@ -200,7 +200,6 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
setOpen(false);
}}
isSelected={validQueryIndex === option.value}
showCheck={false}
>
{option.label}
</ComboboxItem>

View File

@@ -11,6 +11,8 @@
&__title {
font-weight: 500;
font-size: 15px;
padding: 0;
margin: 0;
}
&__container {
@@ -19,7 +21,7 @@
background-color: var(--primary-background);
color: var(--primary-foreground);
border-radius: 8px;
padding: 16px;
padding: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
}
@@ -43,14 +45,23 @@
&__footer {
display: flex;
justify-content: flex-end;
}
&__button {
background: var(--secondary-background);
color: var(--secondary-foreground);
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
// TODO: Need to override the button styles for this component due to container styles.
// Fix - @aks07
&__button {
margin-top: 12px;
color: var(--base-black);
background-color: var(--base-white);
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: var(--base-white);
color: var(--bg-robin-500);
}
}
}
}

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Button, Typography } from 'antd';
import { Button } from '@signozhq/ui';
import classNames from 'classnames';
import { X } from 'lucide-react';
import { Check, X } from 'lucide-react';
import './AnnouncementTooltip.styles.scss';
@@ -46,13 +46,12 @@ function AnnouncementTooltip({
className={classNames('announcement-tooltip__container', className)}
style={{
top: position.top,
left: position.left + 30,
left: position.left + 20,
}}
>
<div className="announcement-tooltip__header">
<Typography.Text className="announcement-tooltip__title">
{title}
</Typography.Text>
<p className="announcement-tooltip__title">{title}</p>
<X
size={18}
onClick={closeTooltip}
@@ -61,7 +60,13 @@ function AnnouncementTooltip({
</div>
<p className="announcement-tooltip__message">{message}</p>
<div className="announcement-tooltip__footer">
<Button onClick={closeTooltip} className="announcement-tooltip__button">
<Button
variant="solid"
color="primary"
onClick={closeTooltip}
prefix={<Check size={16} />}
className="announcement-tooltip__footer__button"
>
Okay
</Button>
</div>

View File

@@ -17,7 +17,7 @@
}
&__label {
font-size: var(--font-size-sm);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-normal);
color: var(--foreground);
line-height: var(--line-height-20);
@@ -142,6 +142,10 @@
gap: var(--spacing-2);
}
&__callout-wrapper {
display: flex;
}
&__expiry-label {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);

View File

@@ -1,7 +1,5 @@
import { Button } from '@signozhq/button';
import { Callout } from '@signozhq/callout';
import { Check, Copy } from '@signozhq/icons';
import { Badge } from '@signozhq/ui';
import { Badge, Button, Callout } from '@signozhq/ui';
import type { ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO } from 'api/generated/services/sigNoz.schemas';
export interface KeyCreatedPhaseProps {
@@ -40,11 +38,13 @@ function KeyCreatedPhase({
<Badge color="vanilla">{expiryLabel}</Badge>
</div>
<Callout
type="info"
showIcon
message="Store the key securely. This is the only time it will be displayed."
/>
<div className="add-key-modal__callout-wrapper">
<Callout
type="info"
showIcon
title="Store the key securely. This is the only time it will be displayed."
/>
</div>
</div>
);
}

View File

@@ -1,8 +1,6 @@
import type { Control, UseFormRegister } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { Button } from '@signozhq/button';
import { Input } from '@signozhq/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
import { Button, Input, ToggleGroup, ToggleGroupItem } from '@signozhq/ui';
import { DatePicker } from 'antd';
import { popupContainer } from 'utils/selectPopupContainer';
@@ -56,11 +54,12 @@ function KeyFormPhase({
<ToggleGroup
type="single"
value={field.value}
onValueChange={(val): void => {
onChange={(val): void => {
if (val) {
field.onChange(val);
}
}}
size="sm"
className="add-key-modal__expiry-toggle"
>
<ToggleGroupItem
@@ -112,6 +111,7 @@ function KeyFormPhase({
</Button>
<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

@@ -2,8 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { DialogWrapper } from '@signozhq/dialog';
import { toast } from '@signozhq/ui';
import { DialogWrapper, toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,
@@ -118,12 +117,12 @@ function AddKeyModal(): JSX.Element {
copyToClipboard(createdKey.key);
setHasCopied(true);
setTimeout(() => setHasCopied(false), 2000);
toast.success('Key copied to clipboard', { richColors: true });
toast.success('Key copied to clipboard');
}, [copyToClipboard, createdKey?.key]);
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy key', { richColors: true });
toast.error('Failed to copy key');
}
}, [copyState.error]);

View File

@@ -1,8 +1,6 @@
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { Trash2, X } from '@signozhq/icons';
import { toast } from '@signozhq/ui';
import { Button, DialogWrapper, toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getGetServiceAccountQueryKey,
@@ -42,7 +40,7 @@ function DeleteAccountModal(): JSX.Element {
} = useDeleteServiceAccount({
mutation: {
onSuccess: async () => {
toast.success('Service account deleted', { richColors: true });
toast.success('Service account deleted');
await setIsDeleteOpen(null);
await setAccountId(null);
await invalidateListServiceAccounts(queryClient);
@@ -70,6 +68,32 @@ function DeleteAccountModal(): JSX.Element {
setIsDeleteOpen(null);
}
const content = (
<p className="sa-delete-dialog__body">
Are you sure you want to delete <strong>{accountName}</strong>? This action
cannot be undone. All keys associated with this service account will be
permanently removed.
</p>
);
const footer = (
<div className="sa-delete-dialog__footer">
<Button variant="solid" color="secondary" onClick={handleCancel}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
loading={isDeleting}
onClick={handleConfirm}
>
<Trash2 size={12} />
Delete
</Button>
</div>
);
return (
<DialogWrapper
open={open}
@@ -83,28 +107,9 @@ function DeleteAccountModal(): JSX.Element {
className="alert-dialog sa-delete-dialog"
showCloseButton={false}
disableOutsideClick={isErrorModalVisible}
footer={footer}
>
<p className="sa-delete-dialog__body">
Are you sure you want to delete <strong>{accountName}</strong>? This action
cannot be undone. All keys associated with this service account will be
permanently removed.
</p>
<DialogFooter className="sa-delete-dialog__footer">
<Button variant="solid" color="secondary" size="sm" onClick={handleCancel}>
<X size={12} />
Cancel
</Button>
<Button
variant="solid"
color="destructive"
size="sm"
loading={isDeleting}
onClick={handleConfirm}
>
<Trash2 size={12} />
Delete
</Button>
</DialogFooter>
{content}
</DialogWrapper>
);
}

View File

@@ -1,10 +1,13 @@
import type { Control, UseFormRegister } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { Button } from '@signozhq/button';
import { LockKeyhole, Trash2, X } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
import { Badge } from '@signozhq/ui';
import {
Badge,
Button,
Input,
ToggleGroup,
ToggleGroupItem,
} from '@signozhq/ui';
import { DatePicker } from 'antd';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import { popupContainer } from 'utils/selectPopupContainer';
@@ -72,11 +75,12 @@ function EditKeyForm({
<ToggleGroup
type="single"
value={field.value}
onValueChange={(val): void => {
onChange={(val): void => {
if (val) {
field.onChange(val);
}
}}
size="sm"
className="edit-key-modal__expiry-toggle"
>
<ToggleGroupItem
@@ -132,25 +136,21 @@ function EditKeyForm({
</form>
<div className="edit-key-modal__footer">
<Button
type="button"
className="edit-key-modal__footer-danger"
onClick={onRevokeClick}
>
<Button variant="ghost" color="destructive" onClick={onRevokeClick}>
<Trash2 size={12} />
Revoke Key
</Button>
<div className="edit-key-modal__footer-right">
<Button variant="solid" color="secondary" size="sm" onClick={onClose}>
<Button variant="solid" color="secondary" onClick={onClose}>
<X size={12} />
Cancel
</Button>
<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"
size="sm"
loading={isSaving}
disabled={!isDirty}
>

View File

@@ -138,7 +138,7 @@
&__meta {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
gap: var(--spacing-4);
}
&__meta-label {

View File

@@ -1,8 +1,7 @@
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { DialogWrapper } from '@signozhq/dialog';
import { toast } from '@signozhq/ui';
import { DialogWrapper, toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,
@@ -72,7 +71,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
const { mutate: updateKey, isLoading: isSaving } = useUpdateServiceAccountKey({
mutation: {
onSuccess: async () => {
toast.success('Key updated successfully', { richColors: true });
toast.success('Key updated successfully');
await setEditKeyId(null);
if (selectedAccountId) {
await invalidateListServiceAccountKeys(queryClient, {
@@ -96,7 +95,7 @@ function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
} = useRevokeServiceAccountKey({
mutation: {
onSuccess: async () => {
toast.success('Key revoked successfully', { richColors: true });
toast.success('Key revoked successfully');
setIsRevokeConfirmOpen(false);
await setEditKeyId(null);
if (selectedAccountId) {

View File

@@ -1,6 +1,6 @@
import { useCallback, useMemo } from 'react';
import { Button } from '@signozhq/button';
import { KeyRound, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui';
import { Skeleton, Table, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
@@ -96,7 +96,7 @@ function buildColumns({
<Tooltip title={isDisabled ? 'Service account disabled' : 'Revoke Key'}>
<Button
variant="ghost"
size="xs"
size="sm"
color="destructive"
disabled={isDisabled}
onClick={(e): void => {
@@ -177,8 +177,8 @@ function KeysTab({
</a>
</p>
<Button
type="button"
className="keys-tab__learn-more"
variant="link"
color="primary"
onClick={async (): Promise<void> => {
await setIsAddKeyOpen(true);
}}

View File

@@ -1,7 +1,6 @@
import { useCallback } from 'react';
import { LockKeyhole } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { Badge } from '@signozhq/ui';
import { Badge, Input } from '@signozhq/ui';
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
import RolesSelect from 'components/RolesSelect';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';

View File

@@ -1,8 +1,6 @@
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { Trash2, X } from '@signozhq/icons';
import { toast } from '@signozhq/ui';
import { Button, DialogWrapper, toast } from '@signozhq/ui';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getListServiceAccountKeysQueryKey,
@@ -36,7 +34,7 @@ export function RevokeKeyContent({
Revoking this key will permanently invalidate it. Any systems using this key
will lose access immediately.
</p>
<DialogFooter className="delete-dialog__footer">
<div className="delete-dialog__footer">
<Button variant="solid" color="secondary" size="sm" onClick={onCancel}>
<X size={12} />
Cancel
@@ -51,7 +49,7 @@ export function RevokeKeyContent({
<Trash2 size={12} />
Revoke Key
</Button>
</DialogFooter>
</div>
</>
);
}
@@ -79,7 +77,7 @@ function RevokeKeyModal(): JSX.Element {
} = useRevokeServiceAccountKey({
mutation: {
onSuccess: async () => {
toast.success('Key revoked successfully', { richColors: true });
toast.success('Key revoked successfully');
await setRevokeKeyId(null);
if (accountId) {
await invalidateListServiceAccountKeys(queryClient, { id: accountId });

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Button } from '@signozhq/button';
import { Color } from '@signozhq/design-tokens';
import { ChevronDown, ChevronUp, CircleAlert, RotateCw } from '@signozhq/icons';
import { Button } from '@signozhq/ui';
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
import APIError from 'types/api/error';
@@ -40,9 +40,9 @@ function SaveErrorItem({
</span>
{onRetry && !isRetrying && (
<Button
type="button"
variant="link"
color="none"
aria-label="Retry"
size="xs"
onClick={async (e): Promise<void> => {
e.stopPropagation();
setIsRetrying(true);

View File

@@ -5,31 +5,21 @@
margin-left: var(--margin-2);
}
&__layout {
display: flex;
flex-direction: column;
height: calc(100vh - 48px);
}
&__tabs {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--padding-3) var(--padding-4) var(--padding-2) var(--padding-4);
flex-shrink: 0;
}
&__tab-group {
[data-slot='toggle-group'] {
height: 32px;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
gap: 0;
}
[data-slot='toggle-group-item'] {
height: 32px;
border-radius: 0;
border-left: 1px solid var(--l1-border);
background: transparent;
@@ -40,6 +30,7 @@
padding: 0 var(--padding-7);
gap: var(--spacing-3);
box-shadow: none;
border: none;
&:first-child {
border-left: none;
@@ -88,7 +79,7 @@
&__body {
flex: 1;
overflow-y: auto;
padding: var(--padding-5) var(--padding-4);
padding-top: var(--padding-5);
display: flex;
flex-direction: column;
gap: var(--spacing-8);
@@ -112,14 +103,11 @@
}
&__footer {
height: 56px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--padding-4);
border-top: 1px solid var(--secondary);
background: var(--card);
width: 100%;
}
&__keys-pagination {
@@ -302,7 +290,7 @@
&__icon {
flex-shrink: 0;
color: var(--bg-cherry-500);
color: var(--danger-background);
}
&__title {
@@ -310,7 +298,7 @@
min-width: 0;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--bg-cherry-500);
color: var(--danger-background);
line-height: var(--line-height-18);
letter-spacing: -0.06px;
text-align: left;
@@ -587,6 +575,5 @@
display: flex;
justify-content: flex-end;
gap: var(--spacing-4);
margin-top: var(--margin-6);
}
}

View File

@@ -1,10 +1,13 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQueryClient } from 'react-query';
import { Button } from '@signozhq/button';
import { DrawerWrapper } from '@signozhq/drawer';
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
import { toast } from '@signozhq/ui';
import {
Button,
DrawerWrapper,
toast,
ToggleGroup,
ToggleGroupItem,
} from '@signozhq/ui';
import { Pagination, Skeleton } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
@@ -331,7 +334,6 @@ function ServiceAccountDrawer({
setSaveErrors(errors);
} else {
toast.success('Service account updated successfully', {
richColors: true,
position: 'top-right',
});
onSuccess({ closeDrawer: false });
@@ -379,7 +381,7 @@ function ServiceAccountDrawer({
<ToggleGroup
type="single"
value={activeTab}
onValueChange={(val): void => {
onChange={(val): void => {
if (val) {
setActiveTab(val as ServiceAccountDrawerTab);
if (val !== ServiceAccountDrawerTab.Keys) {
@@ -471,69 +473,64 @@ function ServiceAccountDrawer({
</>
)}
</div>
</div>
);
<div className="sa-drawer__footer">
{activeTab === ServiceAccountDrawerTab.Keys ? (
<Pagination
current={keysPage}
pageSize={PAGE_SIZE}
total={keys.length}
showTotal={(total: number, range: number[]): JSX.Element => (
<>
<span className="sa-drawer__pagination-range">
{range[0]} &#8212; {range[1]}
</span>
<span className="sa-drawer__pagination-total"> of {total}</span>
</>
)}
showSizeChanger={false}
hideOnSinglePage
onChange={(page): void => {
void setKeysPage(page);
}}
className="sa-drawer__keys-pagination"
/>
) : (
<>
{!isDeleted && (
<Button
variant="ghost"
color="destructive"
className="sa-drawer__footer-btn"
onClick={(): void => {
setIsDeleteOpen(true);
}}
>
<Trash2 size={12} />
Delete Service Account
const footer = (
<div className="sa-drawer__footer">
{activeTab === ServiceAccountDrawerTab.Keys ? (
<Pagination
current={keysPage}
pageSize={PAGE_SIZE}
total={keys.length}
showTotal={(total: number, range: number[]): JSX.Element => (
<>
<span className="sa-drawer__pagination-range">
{range[0]} &#8212; {range[1]}
</span>
<span className="sa-drawer__pagination-total"> of {total}</span>
</>
)}
showSizeChanger={false}
hideOnSinglePage
onChange={(page): void => {
void setKeysPage(page);
}}
className="sa-drawer__keys-pagination"
/>
) : (
<>
{!isDeleted && (
<Button
variant="link"
color="destructive"
onClick={(): void => {
setIsDeleteOpen(true);
}}
>
<Trash2 size={12} />
Delete Service Account
</Button>
)}
{!isDeleted && (
<div className="sa-drawer__footer-right">
<Button variant="solid" color="secondary" onClick={handleClose}>
<X size={14} />
Cancel
</Button>
)}
{!isDeleted && (
<div className="sa-drawer__footer-right">
<Button
variant="solid"
color="secondary"
size="sm"
onClick={handleClose}
>
<X size={14} />
Cancel
</Button>
<Button
variant="solid"
color="primary"
size="sm"
loading={isSaving}
disabled={!isDirty}
onClick={handleSave}
>
Save Changes
</Button>
</div>
)}
</>
)}
</div>
<Button
variant="solid"
color="primary"
loading={isSaving}
disabled={!isDirty}
onClick={handleSave}
>
Save Changes
</Button>
</div>
)}
</>
)}
</div>
);
@@ -547,14 +544,15 @@ function ServiceAccountDrawer({
}
}}
direction="right"
type="panel"
showCloseButton
showOverlay={false}
allowOutsideClick
header={{ title: 'Service Account Details' }}
content={drawerContent}
title="Service Account Details"
className="sa-drawer"
/>
width="wide"
footer={footer}
>
{drawerContent}
</DrawerWrapper>
<DeleteAccountModal />

View File

@@ -1,7 +1,13 @@
import { toast } from '@signozhq/ui';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import {
render,
screen,
userEvent,
waitFor,
waitForElementToBeRemoved,
} from 'tests/test-utils';
import AddKeyModal from '../AddKeyModal';
@@ -117,10 +123,7 @@ describe('AddKeyModal', () => {
await waitFor(() => {
expect(mockCopyToClipboard).toHaveBeenCalledWith('snz_abc123xyz456secret');
expect(mockToast.success).toHaveBeenCalledWith(
'Key copied to clipboard',
expect.anything(),
);
expect(mockToast.success).toHaveBeenCalledWith('Key copied to clipboard');
});
});
@@ -128,11 +131,9 @@ describe('AddKeyModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
await screen.findByRole('dialog', { name: /Add a New Key/i });
const dialog = await screen.findByRole('dialog', { name: /Add a New Key/i });
await user.click(screen.getByRole('button', { name: /Cancel/i }));
expect(
screen.queryByRole('dialog', { name: /Add a New Key/i }),
).not.toBeInTheDocument();
await waitForElementToBeRemoved(dialog);
});
});

View File

@@ -29,9 +29,14 @@ function renderModal(
account: 'sa-1',
'edit-key': 'key-1',
},
onUrlUpdate?: jest.Mock,
): ReturnType<typeof render> {
return render(
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
<NuqsTestingAdapter
searchParams={searchParams}
hasMemory
onUrlUpdate={onUrlUpdate}
>
<EditKeyModal keyItem={keyItem} />
</NuqsTestingAdapter>,
);
@@ -82,10 +87,7 @@ describe('EditKeyModal (URL-controlled)', () => {
await user.click(screen.getByRole('button', { name: /Save Changes/i }));
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith(
'Key updated successfully',
expect.anything(),
);
expect(mockToast.success).toHaveBeenCalledWith('Key updated successfully');
});
await waitFor(() => {
@@ -97,14 +99,31 @@ describe('EditKeyModal (URL-controlled)', () => {
it('cancel clears edit-key param and closes modal', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const onUrlUpdate = jest.fn();
renderModal(mockKey, undefined, onUrlUpdate);
await screen.findByDisplayValue('Original Key Name');
await user.click(screen.getByRole('button', { name: /Cancel/i }));
expect(
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
).not.toBeInTheDocument();
await waitFor(() => {
expect(onUrlUpdate).toHaveBeenCalled();
});
const latestUrlUpdate =
onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1]?.[0];
expect(latestUrlUpdate).toEqual(
expect.objectContaining({
queryString: expect.any(String),
}),
);
expect(latestUrlUpdate.queryString).toContain('account=sa-1');
expect(latestUrlUpdate.queryString).not.toContain('edit-key=');
await waitFor(() => {
expect(
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
).not.toBeInTheDocument();
});
});
it('revoke flow: clicking Revoke Key shows confirmation inside same dialog', async () => {
@@ -136,10 +155,7 @@ describe('EditKeyModal (URL-controlled)', () => {
await user.click(confirmBtn);
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith(
'Key revoked successfully',
expect.anything(),
);
expect(mockToast.success).toHaveBeenCalledWith('Key revoked successfully');
});
await waitFor(() => {

View File

@@ -164,10 +164,7 @@ describe('KeysTab', () => {
await user.click(confirmBtn);
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith(
'Key revoked successfully',
expect.anything(),
);
expect(mockToast.success).toHaveBeenCalledWith('Key revoked successfully');
});
});

View File

@@ -6,18 +6,23 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import ServiceAccountDrawer from '../ServiceAccountDrawer';
jest.mock('@signozhq/drawer', () => ({
DrawerWrapper: ({
content,
open,
}: {
content?: ReactNode;
open: boolean;
}): JSX.Element | null => (open ? <div>{content}</div> : null),
}));
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
DrawerWrapper: ({
children,
footer,
open,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
}): JSX.Element | null =>
open ? (
<div>
{children}
{footer}
</div>
) : null,
toast: { success: jest.fn(), error: jest.fn() },
}));

View File

@@ -21,7 +21,7 @@
.ant-table-thead {
> tr > th,
> tr > td {
background: var(--background);
background: var(--l1-background);
font-size: var(--paragraph-small-600-font-size);
font-weight: var(--paragraph-small-600-font-weight);
line-height: var(--paragraph-small-600-line-height);

View File

@@ -11,7 +11,7 @@
gap: 20px;
padding: 8px 12px;
background: var(--background);
background: var(--l1-background);
color: var(--l2-foreground);
border-radius: 8px;

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