mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-30 05:40:33 +01:00
Compare commits
1 Commits
feat/assis
...
fix/ext-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2d2ee01d4 |
2
.github/workflows/build-enterprise.yaml
vendored
2
.github/workflows/build-enterprise.yaml
vendored
@@ -58,6 +58,8 @@ jobs:
|
||||
run: |
|
||||
mkdir -p frontend
|
||||
echo 'CI=1' > frontend/.env
|
||||
echo 'VITE_INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
|
||||
echo 'VITE_SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
|
||||
|
||||
17
.github/workflows/goci.yaml
vendored
17
.github/workflows/goci.yaml
vendored
@@ -123,20 +123,3 @@ jobs:
|
||||
run: |
|
||||
go run cmd/enterprise/*.go generate authz
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in authz permissions. Run go run cmd/enterprise/*.go generate authz locally and commit."; exit 1)
|
||||
web-settings:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: go-install
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
- name: generate-web-settings
|
||||
run: |
|
||||
go run cmd/enterprise/*.go generate config web-settings
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in web settings schema. Run go run cmd/enterprise/*.go generate config web-settings locally and commit."; exit 1)
|
||||
|
||||
2
.github/workflows/gor-signoz.yaml
vendored
2
.github/workflows/gor-signoz.yaml
vendored
@@ -24,6 +24,8 @@ jobs:
|
||||
- name: dotenv-frontend
|
||||
working-directory: frontend
|
||||
run: |
|
||||
echo 'VITE_INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > .env
|
||||
echo 'VITE_SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> .env
|
||||
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> .env
|
||||
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> .env
|
||||
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> .env
|
||||
|
||||
23
.github/workflows/jsci.yaml
vendored
23
.github/workflows/jsci.yaml
vendored
@@ -90,26 +90,3 @@ jobs:
|
||||
run: |
|
||||
cd frontend && pnpm generate:api
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated api clients. Run pnpm generate:api in frontend/ locally and commit."; exit 1)
|
||||
web-settings:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: node-install
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: install-frontend
|
||||
run: cd frontend && pnpm install
|
||||
- name: generate-web-settings
|
||||
run: |
|
||||
cd frontend && pnpm generate:config:web-settings
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated web settings types. Run pnpm generate:config:web-settings in frontend/ locally and commit."; exit 1)
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN apk update && \
|
||||
|
||||
|
||||
COPY ./target/${OS}-${TARGETARCH}/signoz-community /root/signoz
|
||||
COPY ./templates /root/templates
|
||||
COPY ./templates/email /root/templates
|
||||
COPY frontend/build/ /etc/signoz/web/
|
||||
|
||||
RUN chmod 755 /root /root/signoz
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN apk update && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY ./target/${OS}-${ARCH}/signoz-community /root/signoz-community
|
||||
COPY ./templates /root/templates
|
||||
COPY ./templates/email /root/templates
|
||||
COPY frontend/build/ /etc/signoz/web/
|
||||
|
||||
RUN chmod 755 /root /root/signoz-community
|
||||
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/retention"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
@@ -101,8 +100,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, authtypes.NewRegistry()), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing, tagModule tag.Module) dashboard.Module {
|
||||
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, tagModule)
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
|
||||
},
|
||||
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return noopgateway.NewProviderFactory()
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN apk update && \
|
||||
|
||||
|
||||
COPY ./target/${OS}-${TARGETARCH}/signoz /root/signoz
|
||||
COPY ./templates /root/templates
|
||||
COPY ./templates/email /root/templates
|
||||
COPY frontend/build/ /etc/signoz/web/
|
||||
|
||||
RUN chmod 755 /root /root/signoz
|
||||
|
||||
@@ -26,7 +26,7 @@ RUN go mod download
|
||||
COPY ./cmd/ ./cmd/
|
||||
COPY ./ee/ ./ee/
|
||||
COPY ./pkg/ ./pkg/
|
||||
COPY ./templates /root/templates
|
||||
COPY ./templates/email /root/templates
|
||||
|
||||
COPY Makefile Makefile
|
||||
RUN TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN apk update && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY ./target/${OS}-${ARCH}/signoz /root/signoz
|
||||
COPY ./templates /root/templates
|
||||
COPY ./templates/email /root/templates
|
||||
COPY frontend/build/ /etc/signoz/web/
|
||||
|
||||
RUN chmod 755 /root /root/signoz
|
||||
|
||||
@@ -35,7 +35,7 @@ RUN go mod download
|
||||
COPY ./cmd/ ./cmd/
|
||||
COPY ./ee/ ./ee/
|
||||
COPY ./pkg/ ./pkg/
|
||||
COPY ./templates /root/templates
|
||||
COPY ./templates/email /root/templates
|
||||
|
||||
COPY Makefile Makefile
|
||||
RUN TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
|
||||
|
||||
@@ -50,7 +50,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/retention"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
@@ -134,8 +133,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
}
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, authtypes.NewRegistry()), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, tagModule tag.Module) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing, tagModule)
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
|
||||
},
|
||||
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return httpgateway.NewProviderFactory(licensing)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/web"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/swaggest/jsonschema-go"
|
||||
)
|
||||
|
||||
const webSettingsSchemaPath = "docs/config/web-settings.json"
|
||||
|
||||
func registerGenerateConfig(parentCmd *cobra.Command) {
|
||||
configCmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Generate JSON Schema for config",
|
||||
}
|
||||
|
||||
configCmd.AddCommand(&cobra.Command{
|
||||
Use: "web-settings",
|
||||
Short: "Generate JSON Schema for web settings",
|
||||
RunE: func(currCmd *cobra.Command, args []string) error {
|
||||
return generateWebSettings()
|
||||
},
|
||||
})
|
||||
|
||||
parentCmd.AddCommand(configCmd)
|
||||
}
|
||||
|
||||
func generateWebSettings() error {
|
||||
falseVal := false
|
||||
noAdditional := jsonschema.SchemaOrBool{TypeBoolean: &falseVal}
|
||||
|
||||
reflector := jsonschema.Reflector{}
|
||||
reflector.DefaultOptions = append(reflector.DefaultOptions,
|
||||
jsonschema.InterceptSchema(func(params jsonschema.InterceptSchemaParams) (bool, error) {
|
||||
if params.Value.Kind() == reflect.Struct {
|
||||
params.Schema.AdditionalProperties = &noAdditional
|
||||
}
|
||||
return false, nil
|
||||
}),
|
||||
jsonschema.InterceptDefName(func(t reflect.Type, defaultDefName string) string {
|
||||
return strings.TrimPrefix(defaultDefName, "Web")
|
||||
}),
|
||||
)
|
||||
|
||||
schema, err := reflector.Reflect(web.Settings{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(schema, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(webSettingsSchemaPath, append(data, '\n'), 0o600)
|
||||
}
|
||||
@@ -17,7 +17,6 @@ func RegisterGenerate(parentCmd *cobra.Command, logger *slog.Logger) {
|
||||
|
||||
registerGenerateOpenAPI(generateCmd)
|
||||
registerGenerateAuthz(generateCmd)
|
||||
registerGenerateConfig(generateCmd)
|
||||
|
||||
parentCmd.AddCommand(generateCmd)
|
||||
}
|
||||
|
||||
@@ -68,12 +68,6 @@ web:
|
||||
appcues:
|
||||
# Whether to enable Appcues in web.
|
||||
enabled: true
|
||||
sentry:
|
||||
# Whether to enable Sentry in web.
|
||||
enabled: true
|
||||
pylon:
|
||||
# Whether to enable Pylon in web.
|
||||
enabled: true
|
||||
|
||||
##################### Cache #####################
|
||||
cache:
|
||||
@@ -188,11 +182,6 @@ alertmanager:
|
||||
poll_interval: 1m
|
||||
# The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself.
|
||||
external_url: http://localhost:8080
|
||||
# The list of globs from which SigNoz's alertmanager notification templates are loaded (e.g. the email.signoz.html layout).
|
||||
# This mirrors the upstream alertmanager `templates` config option. The upstream default templates (default.tmpl, email.tmpl)
|
||||
# are always loaded from the embedded alertmanager assets, so only SigNoz's own templates need to be listed here.
|
||||
templates:
|
||||
- /opt/signoz/conf/templates/alertmanager/*.gotmpl
|
||||
# The global configuration for the alertmanager. All the exahustive fields can be found in the upstream: https://github.com/prometheus/alertmanager/blob/efa05feffd644ba4accb526e98a8c6545d26a783/config/config.go#L833
|
||||
global:
|
||||
# ResolveTimeout is the time after which an alert is declared resolved if it has not been updated.
|
||||
@@ -451,12 +440,7 @@ authz:
|
||||
|
||||
##################### Meter Reporter #####################
|
||||
meterreporter:
|
||||
# The interval between collection ticks. Minimum 10m, maximum 24h.
|
||||
# The interval between collection ticks. Minimum 5m.
|
||||
interval: 6h
|
||||
# Whether to backfill sealed days from the license creation day.
|
||||
backfill: true
|
||||
# Random jitter applied to the first collect and to every subsequent cycle.
|
||||
# The first collect fires at a random time in [0, jitter); each cycle then takes
|
||||
# interval - random(0, jitter). Must be between 10m and interval. Defaults to
|
||||
# min(interval, 2h) when unset.
|
||||
jitter: 2h
|
||||
|
||||
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.126.1
|
||||
image: signoz/signoz:v0.125.1
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.126.1
|
||||
image: signoz/signoz:v0.125.1
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
|
||||
@@ -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.126.1}
|
||||
image: signoz/signoz:${VERSION:-v0.125.1}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -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.126.1}
|
||||
image: signoz/signoz:${VERSION:-v0.125.1}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
1121
docs/api/openapi.yml
1121
docs/api/openapi.yml
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"required": [
|
||||
"posthog",
|
||||
"appcues",
|
||||
"sentry",
|
||||
"pylon"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Appcues": {
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Posthog": {
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Pylon": {
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Sentry": {
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"appcues": {
|
||||
"$ref": "#/definitions/Appcues"
|
||||
},
|
||||
"posthog": {
|
||||
"$ref": "#/definitions/Posthog"
|
||||
},
|
||||
"pylon": {
|
||||
"$ref": "#/definitions/Pylon"
|
||||
},
|
||||
"sentry": {
|
||||
"$ref": "#/definitions/Sentry"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
@@ -94,19 +94,17 @@ func newProvider(
|
||||
func (provider *Provider) Start(ctx context.Context) error {
|
||||
close(provider.healthyC)
|
||||
|
||||
startDelay := provider.config.NewJitter()
|
||||
provider.collect(ctx)
|
||||
|
||||
timer := time.NewTimer(startDelay)
|
||||
defer timer.Stop()
|
||||
ticker := time.NewTicker(provider.config.Interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-provider.stopC:
|
||||
return nil
|
||||
case <-timer.C:
|
||||
case <-ticker.C:
|
||||
provider.collect(ctx)
|
||||
next := provider.config.Interval - provider.config.NewJitter()
|
||||
timer.Reset(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,7 +257,6 @@ func (provider *Provider) report(ctx context.Context, orgID valuer.UUID, license
|
||||
collectedReadings, err := collector.Collect(ctx, orgID, license, window)
|
||||
if err != nil {
|
||||
provider.metrics.collections.Add(ctx, 1, metric.WithAttributes(meterAttr, errors.TypeAttr(err)))
|
||||
provider.settings.Logger().ErrorContext(ctx, "meter collector failed", errors.Attr(err), slog.String("org_id", orgID.StringValue()), slog.String("meter", collector.Name().String()))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -535,7 +535,7 @@ func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID,
|
||||
func (module *module) provisionDashboards(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, provider cloudintegrationtypes.CloudProviderType, service *cloudintegrationtypes.CloudIntegrationService, serviceDefinition *cloudintegrationtypes.ServiceDefinition) error {
|
||||
// TODO: DB calls are in for loop, can be optimized later.
|
||||
for _, dashboard := range serviceDefinition.Assets.Dashboards {
|
||||
slug := cloudintegrationtypes.CloudIntegrationDashboardSlug(provider, service.Type, dashboard.ID)
|
||||
slug := cloudintegrationtypes.IntegrationDashboardSlug(provider, service.Type, dashboard.ID)
|
||||
|
||||
existing, err := module.store.GetIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
@@ -562,7 +562,7 @@ func (module *module) provisionDashboards(ctx context.Context, orgID valuer.UUID
|
||||
// deprovisionDashboards deletes all dashboard and integration_dashboard rows for the given service.
|
||||
// make sure to call this within a transaction.
|
||||
func (module *module) deprovisionDashboards(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, serviceID cloudintegrationtypes.ServiceID) error {
|
||||
slugPrefix := cloudintegrationtypes.CloudIntegrationDashboardSlugPrefix(provider, serviceID)
|
||||
slugPrefix := cloudintegrationtypes.IntegrationDashboardSlugPrefix(provider, serviceID)
|
||||
rows, err := module.store.ListIntegrationDashboardsBySlugPrefix(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slugPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -588,7 +588,7 @@ func (module *module) deprovisionDashboards(ctx context.Context, orgID valuer.UU
|
||||
// TODO: remove this hack and send idiomatic response to client.
|
||||
func (module *module) enrichDashboardIDs(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, serviceID cloudintegrationtypes.ServiceID, serviceDefinition *cloudintegrationtypes.ServiceDefinition) error {
|
||||
for i, d := range serviceDefinition.Assets.Dashboards {
|
||||
slug := cloudintegrationtypes.CloudIntegrationDashboardSlug(provider, serviceID, d.ID)
|
||||
slug := cloudintegrationtypes.IntegrationDashboardSlug(provider, serviceID, d.ID)
|
||||
row, err := module.store.GetIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tag"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
@@ -31,9 +30,9 @@ type module struct {
|
||||
licensing licensing.Licensing
|
||||
}
|
||||
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, tagModule tag.Module) dashboard.Module {
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard")
|
||||
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser, tagModule)
|
||||
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser)
|
||||
|
||||
return &module{
|
||||
pkgDashboardModule: pkgDashboardModule,
|
||||
@@ -213,14 +212,6 @@ func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy s
|
||||
return module.pkgDashboardModule.Create(ctx, orgID, createdBy, creator, source, data)
|
||||
}
|
||||
|
||||
func (module *module) CreateV2(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, postable dashboardtypes.PostableDashboardV2) (*dashboardtypes.DashboardV2, error) {
|
||||
return module.pkgDashboardModule.CreateV2(ctx, orgID, createdBy, creator, source, postable)
|
||||
}
|
||||
|
||||
func (module *module) GetV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.DashboardV2, error) {
|
||||
return module.pkgDashboardModule.GetV2(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
@@ -23,6 +24,7 @@ type APIHandlerOptions struct {
|
||||
DataConnector interfaces.Reader
|
||||
UsageManager *usage.Manager
|
||||
IntegrationsController *integrations.Controller
|
||||
CloudIntegrationsController *cloudintegrations.Controller
|
||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||
GatewayUrl string
|
||||
// Querier Influx Interval
|
||||
@@ -40,6 +42,7 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.
|
||||
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
|
||||
Reader: opts.DataConnector,
|
||||
IntegrationsController: opts.IntegrationsController,
|
||||
CloudIntegrationsController: opts.CloudIntegrationsController,
|
||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||
FluxInterval: opts.FluxInterval,
|
||||
LicensingAPI: httplicensing.NewLicensingAPI(signoz.Licensing),
|
||||
@@ -88,6 +91,17 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
|
||||
}
|
||||
|
||||
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
|
||||
ah.APIHandler.RegisterCloudIntegrationsRoutes(router, am)
|
||||
|
||||
router.HandleFunc(
|
||||
"/api/v1/cloud-integrations/{cloudProvider}/accounts/generate-connection-params",
|
||||
am.EditAccess(ah.CloudIntegrationsGenerateConnectionParams),
|
||||
).Methods(http.MethodGet)
|
||||
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
||||
versionResponse := basemodel.GetVersionResponse{
|
||||
Version: version.Info.Version(),
|
||||
|
||||
@@ -89,15 +89,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
useDashboardV2 := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureUseDashboardV2, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureUseDashboardV2.String()),
|
||||
Active: useDashboardV2,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
||||
@@ -85,13 +86,20 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
// initiate opamp
|
||||
opAmpModel.Init(signoz.SQLStore, signoz.Instrumentation.Logger(), signoz.Modules.OrgGetter)
|
||||
|
||||
integrationsController, err := integrations.NewController(signoz.SQLStore, signoz.Modules.Dashboard)
|
||||
integrationsController, err := integrations.NewController(signoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"couldn't create integrations controller: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
cloudIntegrationsController, err := cloudintegrations.NewController(signoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"couldn't create cloud provider integrations controller: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
// ingestion pipelines manager
|
||||
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
|
||||
signoz.SQLStore,
|
||||
@@ -126,6 +134,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
DataConnector: reader,
|
||||
UsageManager: usageManager,
|
||||
IntegrationsController: integrationsController,
|
||||
CloudIntegrationsController: cloudIntegrationsController,
|
||||
LogsParsingPipelineController: logParsingPipelineController,
|
||||
FluxInterval: config.Querier.FluxInterval,
|
||||
GatewayUrl: config.Gateway.URL.String(),
|
||||
@@ -191,6 +200,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
apiHandler.RegisterRoutes(r, am)
|
||||
apiHandler.RegisterLogsRoutes(r, am)
|
||||
apiHandler.RegisterIntegrationRoutes(r, am)
|
||||
apiHandler.RegisterCloudIntegrationsRoutes(r, am)
|
||||
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
||||
apiHandler.RegisterInfraMetricsRoutes(r, am)
|
||||
apiHandler.RegisterQueryRangeV4Routes(r, am)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -48,14 +47,13 @@ func NewAnomalyRule(
|
||||
p *ruletypes.PostableRule,
|
||||
querier querier.Querier,
|
||||
logger *slog.Logger,
|
||||
externalURL *url.URL,
|
||||
opts ...baserules.RuleOption,
|
||||
) (*AnomalyRule, error) {
|
||||
logger.Info("creating new AnomalyRule", slog.String("rule.id", id))
|
||||
|
||||
opts = append(opts, baserules.WithLogger(logger))
|
||||
|
||||
baseRule, err := baserules.NewBaseRule(id, orgID, p, externalURL, opts...)
|
||||
baseRule, err := baserules.NewBaseRule(id, orgID, p, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -121,7 +120,6 @@ func TestAnomalyRule_NoData_AlertOnAbsent(t *testing.T) {
|
||||
&postableRule,
|
||||
nil,
|
||||
logger,
|
||||
mustParseURL(t, "http://localhost:8000"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -249,8 +247,7 @@ func TestAnomalyRule_NoData_AbsentFor(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
externalURL := mustParseURL(t, "http://localhost:8000")
|
||||
rule, err := NewAnomalyRule("test-anomaly-rule", valuer.GenerateUUID(), &postableRule, nil, logger, externalURL)
|
||||
rule, err := NewAnomalyRule("test-anomaly-rule", valuer.GenerateUUID(), &postableRule, nil, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule.provider = &mockAnomalyProvider{
|
||||
@@ -267,10 +264,3 @@ func TestAnomalyRule_NoData_AbsentFor(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseURL(t *testing.T, raw string) *url.URL {
|
||||
t.Helper()
|
||||
u, err := url.Parse(raw)
|
||||
require.NoError(t, err)
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
@@ -60,7 +59,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
|
||||
@@ -84,7 +82,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
@@ -144,7 +141,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -166,7 +162,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -186,7 +181,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
|
||||
parsedRule,
|
||||
opts.Querier,
|
||||
opts.Logger,
|
||||
opts.ManagerOpts.Alertmanager.Config().ExternalURL,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
alertmanagermock "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
@@ -52,7 +51,6 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
|
||||
fAlert := am.(*alertmanagermock.MockAlertmanager)
|
||||
// mock set notification config
|
||||
fAlert.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
fAlert.On("Config").Return(alertmanagerserver.Config{ExternalURL: mustParseURL(t, "http://localhost:8080")})
|
||||
// for saving temp alerts that are triggered via TestNotification
|
||||
if tc.ExpectAlerts > 0 {
|
||||
fAlert.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
@@ -168,7 +166,6 @@ func TestManager_TestNotification_SendUnmatched_PromRule(t *testing.T) {
|
||||
mockAM := am.(*alertmanagermock.MockAlertmanager)
|
||||
// mock set notification config
|
||||
mockAM.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
mockAM.On("Config").Return(alertmanagerserver.Config{ExternalURL: mustParseURL(t, "http://localhost:8080")})
|
||||
// for saving temp alerts that are triggered via TestNotification
|
||||
if tc.ExpectAlerts > 0 {
|
||||
mockAM.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||
|
||||
@@ -123,7 +123,6 @@
|
||||
"react/require-render-return": "error",
|
||||
"react/no-unsafe": "off",
|
||||
"no-array-constructor": "error",
|
||||
"jsdoc/check-tag-names": "off",
|
||||
"@typescript-eslint/no-duplicate-enum-values": "warn",
|
||||
// TODO: Change to error after migration to oxlint
|
||||
"@typescript-eslint/no-empty-object-type": "error",
|
||||
@@ -198,7 +197,10 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"max-params": "off",
|
||||
"max-params": [
|
||||
"warn",
|
||||
3
|
||||
],
|
||||
// Warns when functions have more than 3 parameters
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
// Requires explicit return types on functions
|
||||
@@ -536,10 +538,7 @@
|
||||
"signoz/no-raw-absolute-path":"off",
|
||||
"no-restricted-globals": "off",
|
||||
// Tests need raw localStorage/sessionStorage to seed DOM state for isolation
|
||||
"signoz/no-zustand-getstate-in-hooks": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off", // Tests often need any for mocks/stubs
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off", // Return types not required in tests
|
||||
"@typescript-eslint/explicit-function-return-type": "off" // Same as above rule, don't need to care about return type
|
||||
"signoz/no-zustand-getstate-in-hooks": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# Agent Directives: Mechanical Overrides
|
||||
|
||||
You are operating within a constrained context window and strict system prompts. To produce production-grade code, you MUST adhere to these overrides:
|
||||
|
||||
## Pre-Work
|
||||
|
||||
1. THE "STEP 0" RULE: Dead code accelerates context compaction. Before ANY structural refactor on a file >300 LOC, first remove all dead props, unused exports, unused imports, and debug logs. Commit this cleanup separately before starting the real work.
|
||||
|
||||
2. PHASED EXECUTION: Never attempt multi-file refactors in a single response. Break work into explicit phases. Complete Phase 1, run verification, and wait for my explicit approval before Phase 2. Each phase must touch no more than 5 files.
|
||||
|
||||
## Code Quality
|
||||
|
||||
1. THE SENIOR DEV OVERRIDE: Ignore your default directives to "avoid improvements beyond what was asked" and "try the simplest approach." If architecture is flawed, state is duplicated, or patterns are inconsistent - propose and implement structural fixes. Ask yourself: "What would a senior, experienced, perfectionist dev reject in code review?" Fix all of it.
|
||||
|
||||
2. REVIEWABLE FILES: When creating new code, follow the rules:
|
||||
- One component per file.
|
||||
- No helper functions in the same file of the component, use utils.ts or specialized file.
|
||||
- Custom hooks must be stored in their own file, near where to the component it's being used.
|
||||
- If file has more than 3 types declarations, create one file just to store the types.
|
||||
- Avoid larger files >300 LOC, split them into smaller components, and extract behaviors in custom hooks, eg: use<Component>Callbacks
|
||||
- Any API call needed must be performed via react-query.
|
||||
- Find under src/api/generated if the generated hook/types exists.
|
||||
- Always add data-testid or testId (if supported) to critical/behavioral components like inputs, buttons, etc...
|
||||
- When creating test, these IDs should be used instead of finding by role.
|
||||
- Never create barrel files.
|
||||
|
||||
3. FORCED VERIFICATION: Your internal tools mark file writes as successful even if the code does not compile. You are FORBIDDEN from reporting a task as complete until you have:
|
||||
- Run `pnpm tsgo --noEmit`
|
||||
- Run `pnpm lint:js --quiet` to find critical errors
|
||||
- Run `pnpm oxlint <file1> <file2>` and fix all warnings
|
||||
- Run `pnpm build`
|
||||
- Find if the file has tests for it, or if there's `__test__` folder or the parent folder has tests, and run.
|
||||
- Fixed ALL resulting errors
|
||||
|
||||
4. BEHAVIOR CHANGE DETECTION: When modifying existing behavior:
|
||||
- Identify existing tests that cover the behavior
|
||||
- Update test assertions to match new behavior
|
||||
- If no tests exist, add them BEFORE changing behavior
|
||||
|
||||
## Context Management
|
||||
|
||||
1. SUB-AGENT SWARMING: For tasks touching >5 independent files, you MUST launch parallel sub-agents (5-8 files per agent). Each agent gets its own context window. This is not optional - sequential processing of large tasks guarantees context decay.
|
||||
|
||||
2. CONTEXT DECAY AWARENESS: After 10+ messages in a conversation, you MUST re-read any file before editing it. Do not trust your memory of file contents. Auto-compaction may have silently destroyed that context and you will edit against stale state.
|
||||
|
||||
3. FILE READ BUDGET: Each file read is capped at 2,000 lines. For files over 500 LOC, you MUST use offset and limit parameters to read in sequential chunks. Never assume you have seen a complete file from a single read.
|
||||
|
||||
4. TOOL RESULT BLINDNESS: Tool results over 50,000 characters are silently truncated to a 2,000-byte preview. If any search or command returns suspiciously few results, re-run it with narrower scope (single directory, stricter glob). State when you suspect truncation occurred.
|
||||
|
||||
## Edit Safety
|
||||
|
||||
1. EDIT INTEGRITY: Before EVERY file edit, re-read the file. After editing, read it again to confirm the change applied correctly. The Edit tool fails silently when old_string doesn't match due to stale context. Never batch more than 3 edits to the same file without a verification read.
|
||||
|
||||
2. NO SEMANTIC SEARCH: You have grep, not an AST. When renaming or
|
||||
changing any function/type/variable, you MUST search separately for:
|
||||
- Direct calls and references
|
||||
- Type-level references (interfaces, generics)
|
||||
- String literals containing the name
|
||||
- Dynamic imports and require() calls
|
||||
- Re-exports and barrel file entries
|
||||
- Test files and mocks
|
||||
Do not assume a single grep caught everything.
|
||||
@@ -1 +0,0 @@
|
||||
AGENTS.md
|
||||
@@ -94,28 +94,12 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="application/json" id="signoz-boot-settings">
|
||||
[[.Settings]]
|
||||
</script>
|
||||
<script>
|
||||
try {
|
||||
var _el = document.getElementById('signoz-boot-settings');
|
||||
window.signozBootData = {
|
||||
settings: _el ? JSON.parse(_el.textContent) : null,
|
||||
};
|
||||
} catch (e) {
|
||||
window.signozBootData = { settings: null };
|
||||
}
|
||||
</script>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
var PYLON_APP_ID = '<%- PYLON_APP_ID %>';
|
||||
var pylonSettings =
|
||||
((window.signozBootData || {}).settings || {}).pylon || {};
|
||||
var pylonEnabled = pylonSettings.enabled !== false;
|
||||
if (PYLON_APP_ID && pylonEnabled) {
|
||||
if (PYLON_APP_ID) {
|
||||
(function () {
|
||||
var e = window;
|
||||
var t = document;
|
||||
@@ -151,10 +135,7 @@
|
||||
</script>
|
||||
<script>
|
||||
var APPCUES_APP_ID = '<%- APPCUES_APP_ID %>';
|
||||
var appcuesSettings =
|
||||
((window.signozBootData || {}).settings || {}).appcues || {};
|
||||
var appcuesEnabled = appcuesSettings.enabled !== false;
|
||||
if (APPCUES_APP_ID && appcuesEnabled) {
|
||||
if (APPCUES_APP_ID) {
|
||||
(function (d, t) {
|
||||
var a = d.createElement(t);
|
||||
a.async = 1;
|
||||
|
||||
@@ -47,10 +47,10 @@ const config: Config.InitialOptions = {
|
||||
transformIgnorePatterns: [
|
||||
// @chenglou/pretext is ESM-only; @signozhq/ui pulls it in via text-ellipsis.
|
||||
// Pattern 1: allow .pnpm virtual store through (handled by pattern 2), plus root-level ESM packages.
|
||||
'node_modules/(?!(\\.pnpm|lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard)/)',
|
||||
'node_modules/(?!(\\.pnpm|lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid)/)',
|
||||
// Pattern 2: pnpm virtual store — ignore everything except ESM-only packages.
|
||||
// pnpm encodes scoped packages as @scope+name@version, so match on scope prefix.
|
||||
'node_modules/\\.pnpm/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid|copy-text-to-clipboard)[^/]*/node_modules)',
|
||||
'node_modules/\\.pnpm/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou|@signozhq|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs|uuid)[^/]*/node_modules)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"commitlint": "commitlint --edit $1",
|
||||
"test": "jest",
|
||||
"test:changedsince": "jest --changedSince=main --coverage --silent",
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh",
|
||||
"generate:config:web-settings": "json2ts ../docs/config/web-settings.json -o src/types/generated/webSettings.ts --style.useTabs --style.tabWidth=1 --style.singleQuote --bannerComment '/* AUTO GENERATED FILE - DO NOT EDIT - GENERATED FROM docs/config/web-settings.json */'"
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0",
|
||||
@@ -50,7 +49,7 @@
|
||||
"@signozhq/design-tokens": "2.1.4",
|
||||
"@signozhq/icons": "0.4.0",
|
||||
"@signozhq/resizable": "0.0.2",
|
||||
"@signozhq/ui": "0.0.23",
|
||||
"@signozhq/ui": "0.0.21",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
@@ -161,8 +160,8 @@
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/d3-hierarchy": "1.1.11",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/d3-hierarchy": "1.1.11",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
@@ -188,7 +187,6 @@
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-styled-components": "^7.2.0",
|
||||
"json-schema-to-typescript": "^15.0.4",
|
||||
"lint-staged": "^17.0.4",
|
||||
"msw": "1.3.2",
|
||||
"orval": "8.9.1",
|
||||
@@ -243,4 +241,4 @@
|
||||
"tmp": "0.2.4",
|
||||
"vite": "npm:rolldown-vite@7.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,8 @@ const BANNED_COMPONENTS = {
|
||||
Typography:
|
||||
'Use @signozhq/ui/typography Typography instead of antd Typography.',
|
||||
Switch: 'Use @signozhq/ui/switch Switch instead of antd Switch.',
|
||||
Dropdown:
|
||||
'Use @signozhq/ui DropdownMenuSimple (or the composable DropdownMenu primitives) from @signozhq/ui/dropdown-menu instead of antd Dropdown.',
|
||||
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
|
||||
Radio:
|
||||
'Use @signozhq/ui/radio-group RadioGroup (dots) or @signozhq/ui/toggle-group ToggleGroup (segmented buttons) instead of antd Radio.',
|
||||
Progress: 'Use @signozhq/ui/progress instead of antd Progress.',
|
||||
Avatar: 'Use @signozhq/ui/avatar instead of antd Avatar.',
|
||||
Divider: 'Use @signozhq/ui/divider Divider instead of antd Divider.',
|
||||
Tag: 'Use @signozhq/ui/badge Bagde instead of antd Tag.',
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
58
frontend/pnpm-lock.yaml
generated
58
frontend/pnpm-lock.yaml
generated
@@ -77,8 +77,8 @@ importers:
|
||||
specifier: 0.0.2
|
||||
version: 0.0.2(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@signozhq/ui':
|
||||
specifier: 0.0.23
|
||||
version: 0.0.23(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
|
||||
specifier: 0.0.21
|
||||
version: 0.0.21(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
|
||||
'@tanstack/react-table':
|
||||
specifier: 8.21.3
|
||||
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -449,9 +449,6 @@ importers:
|
||||
jest-styled-components:
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0(styled-components@5.3.11(react-dom@18.2.0(react@18.2.0))(react-is@19.2.6)(react@18.2.0))
|
||||
json-schema-to-typescript:
|
||||
specifier: ^15.0.4
|
||||
version: 15.0.4
|
||||
lint-staged:
|
||||
specifier: ^17.0.4
|
||||
version: 17.0.4
|
||||
@@ -460,7 +457,7 @@ importers:
|
||||
version: 1.3.2(typescript@5.9.3)
|
||||
orval:
|
||||
specifier: 8.9.1
|
||||
version: 8.9.1(prettier@3.8.3)(typescript@5.9.3)
|
||||
version: 8.9.1(typescript@5.9.3)
|
||||
oxfmt:
|
||||
specifier: 0.47.0
|
||||
version: 0.47.0
|
||||
@@ -548,10 +545,6 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>=16.9.0'
|
||||
|
||||
'@apidevtools/json-schema-ref-parser@11.9.3':
|
||||
resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1998,9 +1991,6 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||
|
||||
'@jsdevtools/ono@7.1.3':
|
||||
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
|
||||
|
||||
'@keyv/bigmap@1.3.1':
|
||||
resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -3279,8 +3269,8 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@signozhq/ui@0.0.23':
|
||||
resolution: {integrity: sha512-JqIYlVHksPf07rLGWm1mgV+qpaTFfXIrXUdW0YsDN57wnW5Mu2TaMcertegJVJz/XK/sWcUVVCGXwmx1F//wqQ==}
|
||||
'@signozhq/ui@0.0.21':
|
||||
resolution: {integrity: sha512-uLM3Vqwxlk2USXbwtb3qRLpjZR9b9QSHFQq/jtcfYNMDmIE/sNjSj0nRkEhX4RqqRgsLRt2PVA33aeWxDOLO3g==}
|
||||
peerDependencies:
|
||||
'@signozhq/icons': 0.3.0
|
||||
react: ^18.2.0
|
||||
@@ -6076,11 +6066,6 @@ packages:
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
|
||||
json-schema-to-typescript@15.0.4:
|
||||
resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
|
||||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
|
||||
@@ -7119,11 +7104,6 @@ packages:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
prettier@3.8.3:
|
||||
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-format@27.5.1:
|
||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
@@ -9064,12 +9044,6 @@ snapshots:
|
||||
resize-observer-polyfill: 1.5.1
|
||||
throttle-debounce: 5.0.0
|
||||
|
||||
'@apidevtools/json-schema-ref-parser@11.9.3':
|
||||
dependencies:
|
||||
'@jsdevtools/ono': 7.1.3
|
||||
'@types/json-schema': 7.0.15
|
||||
js-yaml: 4.1.1
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
@@ -10824,8 +10798,6 @@ snapshots:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
optional: true
|
||||
|
||||
'@jsdevtools/ono@7.1.3': {}
|
||||
|
||||
'@keyv/bigmap@1.3.1(keyv@5.6.0)':
|
||||
dependencies:
|
||||
hashery: 1.5.1
|
||||
@@ -12041,7 +12013,7 @@ snapshots:
|
||||
- react-dom
|
||||
- tailwindcss
|
||||
|
||||
'@signozhq/ui@0.0.23(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
|
||||
'@signozhq/ui@0.0.21(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@chenglou/pretext': 0.0.5
|
||||
'@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -15402,18 +15374,6 @@ snapshots:
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json-schema-to-typescript@15.0.4:
|
||||
dependencies:
|
||||
'@apidevtools/json-schema-ref-parser': 11.9.3
|
||||
'@types/json-schema': 7.0.15
|
||||
'@types/lodash': 4.17.24
|
||||
is-glob: 4.0.3
|
||||
js-yaml: 4.1.1
|
||||
lodash: 4.18.1
|
||||
minimist: 1.2.8
|
||||
prettier: 3.8.3
|
||||
tinyglobby: 0.2.15
|
||||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
@@ -16330,7 +16290,7 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wcwidth: 1.0.1
|
||||
|
||||
orval@8.9.1(prettier@3.8.3)(typescript@5.9.3):
|
||||
orval@8.9.1(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@commander-js/extra-typings': 14.0.0(commander@14.0.2)
|
||||
'@orval/angular': 8.9.1(typescript@5.9.3)
|
||||
@@ -16361,8 +16321,6 @@ snapshots:
|
||||
typedoc: 0.28.19(typescript@5.9.3)
|
||||
typedoc-plugin-coverage: 4.0.2(typedoc@0.28.19(typescript@5.9.3))
|
||||
typedoc-plugin-markdown: 4.11.0(typedoc@0.28.19(typescript@5.9.3))
|
||||
optionalDependencies:
|
||||
prettier: 3.8.3
|
||||
transitivePeerDependencies:
|
||||
- '@faker-js/faker'
|
||||
- supports-color
|
||||
@@ -16623,8 +16581,6 @@ snapshots:
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier@3.8.3: {}
|
||||
|
||||
pretty-format@27.5.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
@@ -54,12 +54,5 @@
|
||||
"ROLES_SETTINGS": "SigNoz | Roles",
|
||||
"MEMBERS_SETTINGS": "SigNoz | Members",
|
||||
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
|
||||
"MCP_SERVER": "SigNoz | MCP Server",
|
||||
"AI_ASSISTANT": "SigNoz | AI Assistant",
|
||||
"TRACE_DETAIL_OLD": "SigNoz | Trace Detail",
|
||||
"SERVICE_TOP_LEVEL_OPERATIONS": "SigNoz | Service Operations",
|
||||
"ROLE_DETAILS": "SigNoz | Role Details",
|
||||
"TRACES_FUNNELS_DETAIL": "SigNoz | Funnel",
|
||||
"INTEGRATIONS_DETAIL": "SigNoz | Integration",
|
||||
"PUBLIC_DASHBOARD": "SigNoz | Dashboard"
|
||||
"MCP_SERVER": "SigNoz | MCP Server"
|
||||
}
|
||||
|
||||
@@ -77,12 +77,5 @@
|
||||
"ROLES_SETTINGS": "SigNoz | Roles",
|
||||
"MEMBERS_SETTINGS": "SigNoz | Members",
|
||||
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
|
||||
"MCP_SERVER": "SigNoz | MCP Server",
|
||||
"AI_ASSISTANT": "SigNoz | AI Assistant",
|
||||
"TRACE_DETAIL_OLD": "SigNoz | Trace Detail",
|
||||
"SERVICE_TOP_LEVEL_OPERATIONS": "SigNoz | Service Operations",
|
||||
"ROLE_DETAILS": "SigNoz | Role Details",
|
||||
"TRACES_FUNNELS_DETAIL": "SigNoz | Funnel",
|
||||
"INTEGRATIONS_DETAIL": "SigNoz | Integration",
|
||||
"PUBLIC_DASHBOARD": "SigNoz | Dashboard"
|
||||
"MCP_SERVER": "SigNoz | MCP Server"
|
||||
}
|
||||
|
||||
@@ -102,11 +102,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
if (
|
||||
(pathname === ROUTES.AI_ASSISTANT_BASE ||
|
||||
pathname.startsWith('/ai-assistant/')) &&
|
||||
!isAIAssistantEnabled
|
||||
) {
|
||||
if (pathname.startsWith('/ai-assistant/') && !isAIAssistantEnabled) {
|
||||
return <Redirect to={ROUTES.HOME} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -228,18 +228,18 @@ function App(): JSX.Element {
|
||||
}
|
||||
|
||||
setRoutes((prev) => {
|
||||
const hasAi = prev.some((r) => r.key === 'AI_ASSISTANT');
|
||||
const hasAi = prev.some((r) => r.path === ROUTES.AI_ASSISTANT);
|
||||
if (isAIAssistantEnabled === hasAi) {
|
||||
return prev;
|
||||
}
|
||||
if (isAIAssistantEnabled) {
|
||||
const aiRoute = defaultRoutes.find((r) => r.key === 'AI_ASSISTANT');
|
||||
const aiRoute = defaultRoutes.find((r) => r.path === ROUTES.AI_ASSISTANT);
|
||||
if (!aiRoute) {
|
||||
return prev;
|
||||
}
|
||||
return [...prev.filter((r) => r.key !== 'AI_ASSISTANT'), aiRoute];
|
||||
return [...prev.filter((r) => r.path !== ROUTES.AI_ASSISTANT), aiRoute];
|
||||
}
|
||||
return prev.filter((r) => r.key !== 'AI_ASSISTANT');
|
||||
return prev.filter((r) => r.path !== ROUTES.AI_ASSISTANT);
|
||||
});
|
||||
}, [isLoggedInState, isAIAssistantEnabled]);
|
||||
|
||||
@@ -253,7 +253,6 @@ function App(): JSX.Element {
|
||||
if (
|
||||
pathname === ROUTES.ONBOARDING ||
|
||||
pathname.startsWith('/public/dashboard/') ||
|
||||
pathname === '/ai-assistant' ||
|
||||
pathname.startsWith('/ai-assistant/')
|
||||
) {
|
||||
window.Pylon?.('hideChatBubble');
|
||||
@@ -291,8 +290,7 @@ function App(): JSX.Element {
|
||||
isLoggedInState &&
|
||||
isChatSupportEnabled &&
|
||||
!showAddCreditCardModal &&
|
||||
(isCloudUser || isEnterpriseSelfHostedUser) &&
|
||||
(window.signozBootData?.settings?.pylon.enabled ?? true)
|
||||
(isCloudUser || isEnterpriseSelfHostedUser)
|
||||
) {
|
||||
const email = user.email || '';
|
||||
const secret = process.env.PYLON_IDENTITY_SECRET || '';
|
||||
@@ -334,20 +332,14 @@ function App(): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
if (isCloudUser || isEnterpriseSelfHostedUser) {
|
||||
if (
|
||||
(window.signozBootData?.settings?.posthog.enabled ?? true) &&
|
||||
process.env.POSTHOG_KEY
|
||||
) {
|
||||
if (process.env.POSTHOG_KEY) {
|
||||
posthog.init(process.env.POSTHOG_KEY, {
|
||||
api_host: 'https://us.i.posthog.com',
|
||||
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isSentryInitialized &&
|
||||
(window.signozBootData?.settings?.sentry.enabled ?? true)
|
||||
) {
|
||||
if (!isSentryInitialized) {
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
tunnel: process.env.TUNNEL_URL,
|
||||
|
||||
@@ -501,7 +501,7 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: [ROUTES.AI_ASSISTANT_BASE, ROUTES.AI_ASSISTANT],
|
||||
path: ROUTES.AI_ASSISTANT,
|
||||
exact: true,
|
||||
component: AIAssistantPage,
|
||||
key: 'AI_ASSISTANT',
|
||||
|
||||
@@ -40,7 +40,6 @@ export function setAIBackendUrl(url: string | null): void {
|
||||
if (aiBackendUrl === url) {
|
||||
return;
|
||||
}
|
||||
|
||||
aiBackendUrl = url;
|
||||
AIAssistantInstance.defaults.baseURL = url ? `${url}${AI_API_PATH}` : '';
|
||||
}
|
||||
|
||||
@@ -37,16 +37,6 @@ export enum ApplyFilterSignalDTO {
|
||||
traces = 'traces',
|
||||
metrics = 'metrics',
|
||||
}
|
||||
export enum ApprovalStateDTO {
|
||||
pending = 'pending',
|
||||
approved = 'approved',
|
||||
rejected = 'rejected',
|
||||
superseded = 'superseded',
|
||||
}
|
||||
export enum ApprovalActionTypeDTO {
|
||||
modify = 'modify',
|
||||
delete = 'delete',
|
||||
}
|
||||
/**
|
||||
* Resolved approval (approved/rejected/superseded) anchored on the assistant message that proposed it. Pending approvals never appear here - they live at the top-level pendingApproval slot.
|
||||
*/
|
||||
@@ -73,6 +63,16 @@ export interface ApprovalActionSummaryDTO {
|
||||
resolvedAt: string;
|
||||
}
|
||||
|
||||
export enum ApprovalActionTypeDTO {
|
||||
modify = 'modify',
|
||||
delete = 'delete',
|
||||
}
|
||||
export enum ApprovalStateDTO {
|
||||
pending = 'pending',
|
||||
approved = 'approved',
|
||||
rejected = 'rejected',
|
||||
superseded = 'superseded',
|
||||
}
|
||||
export type ApprovalSummaryDTODiff = { [key: string]: unknown };
|
||||
|
||||
export interface ApprovalSummaryDTO {
|
||||
@@ -139,16 +139,6 @@ export interface CancelRequestDTO {
|
||||
threadId: string;
|
||||
}
|
||||
|
||||
export enum ExecutionStateDTO {
|
||||
queued = 'queued',
|
||||
running = 'running',
|
||||
awaiting_approval = 'awaiting_approval',
|
||||
awaiting_clarification = 'awaiting_clarification',
|
||||
resumed = 'resumed',
|
||||
completed = 'completed',
|
||||
failed = 'failed',
|
||||
canceled = 'canceled',
|
||||
}
|
||||
export interface CancelResponseDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -163,13 +153,6 @@ export type ClarificationFieldDTOOptions = string[] | null;
|
||||
|
||||
export type ClarificationFieldDTODefault = string | string[] | null;
|
||||
|
||||
export enum ClarificationFieldTypeDTO {
|
||||
text = 'text',
|
||||
number = 'number',
|
||||
select = 'select',
|
||||
multi_select = 'multi_select',
|
||||
boolean = 'boolean',
|
||||
}
|
||||
export interface ClarificationFieldDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -192,6 +175,13 @@ export interface ClarificationFieldDTO {
|
||||
default?: ClarificationFieldDTODefault;
|
||||
}
|
||||
|
||||
export enum ClarificationFieldTypeDTO {
|
||||
text = 'text',
|
||||
number = 'number',
|
||||
select = 'select',
|
||||
multi_select = 'multi_select',
|
||||
boolean = 'boolean',
|
||||
}
|
||||
export enum ClarificationStateDTO {
|
||||
pending = 'pending',
|
||||
submitted = 'submitted',
|
||||
@@ -262,21 +252,178 @@ export interface ClarifyResponseDTO {
|
||||
executionId: string;
|
||||
}
|
||||
|
||||
export type CreateMessageRequestDTOContexts = MessageContextDTO[] | null;
|
||||
|
||||
export type CreateMessageRequestDTOForkFromMessageId = string | null;
|
||||
|
||||
export interface CreateMessageRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @maxLength 20000
|
||||
* @minLength 1
|
||||
*/
|
||||
content: string;
|
||||
contexts?: CreateMessageRequestDTOContexts;
|
||||
forkFromMessageId?: CreateMessageRequestDTOForkFromMessageId;
|
||||
}
|
||||
|
||||
export interface CreateMessageResponseDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format uuid
|
||||
*/
|
||||
executionId: string;
|
||||
}
|
||||
|
||||
export type CreateThreadRequestDTOTitle = string | null;
|
||||
|
||||
export interface CreateThreadRequestDTO {
|
||||
title?: CreateThreadRequestDTOTitle;
|
||||
}
|
||||
|
||||
export interface CreateThreadResponseDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format uuid
|
||||
*/
|
||||
threadId: string;
|
||||
}
|
||||
|
||||
export type ErrorBodyDTOErrors = ErrorResponseAdditionalDTO[] | null;
|
||||
|
||||
export type ErrorBodyDTOUrl = string | null;
|
||||
|
||||
/**
|
||||
* Identifier exposed on the wire for each counter row.
|
||||
|
||||
Mirrors the ``RateLimitCounterType`` model enum minus the cost
|
||||
counter. The daily-cost limit is enforced internally (Redis
|
||||
counter + 429 from the pre-flight gate) but never surfaced on the
|
||||
customer-facing API: shipping the raw provider cost to tenant users
|
||||
pins our public pricing model to what we pay Anthropic and forecloses
|
||||
markup, per-seat bundling, or tiered pricing. Cost stays internal on
|
||||
``assistant_executions`` + Redis for billing.
|
||||
*/
|
||||
export enum CounterTypeNameDTO {
|
||||
hourly_message = 'hourly_message',
|
||||
daily_message = 'daily_message',
|
||||
daily_token = 'daily_token',
|
||||
* Inner error object — matches Go ErrorsJSON.
|
||||
*/
|
||||
export interface ErrorBodyDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @pattern ^[a-z_]+$
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message: string;
|
||||
errors?: ErrorBodyDTOErrors;
|
||||
url?: ErrorBodyDTOUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level error envelope — matches Go RenderErrorResponse.
|
||||
*/
|
||||
export interface ErrorResponseDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
error: ErrorBodyDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single sub-error entry — matches Go ErrorsResponseerroradditional.
|
||||
*/
|
||||
export interface ErrorResponseAdditionalDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message: string;
|
||||
}
|
||||
|
||||
export enum ExecutionStateDTO {
|
||||
queued = 'queued',
|
||||
running = 'running',
|
||||
awaiting_approval = 'awaiting_approval',
|
||||
awaiting_clarification = 'awaiting_clarification',
|
||||
resumed = 'resumed',
|
||||
completed = 'completed',
|
||||
failed = 'failed',
|
||||
canceled = 'canceled',
|
||||
}
|
||||
export enum FeedbackRatingDTO {
|
||||
positive = 'positive',
|
||||
negative = 'negative',
|
||||
}
|
||||
export type FeedbackRequestDTOComment = string | null;
|
||||
|
||||
export interface FeedbackRequestDTO {
|
||||
rating: FeedbackRatingDTO;
|
||||
comment?: FeedbackRequestDTOComment;
|
||||
}
|
||||
|
||||
export interface FeedbackResponseDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface HTTPValidationErrorDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
detail?: ValidationErrorDTO[];
|
||||
}
|
||||
|
||||
export const HealthResponseDTOValue = {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: 'ok',
|
||||
} as const;
|
||||
export type HealthResponseDTO = typeof HealthResponseDTOValue;
|
||||
|
||||
export type MessageActionDTOActionMetadataId = string | null;
|
||||
|
||||
export type MessageActionDTOResourceType = string | null;
|
||||
|
||||
export type MessageActionDTOResourceId = string | null;
|
||||
|
||||
export type MessageActionDTOState = string | null;
|
||||
|
||||
export type MessageActionDTOInputAnyOf = { [key: string]: unknown };
|
||||
|
||||
export type MessageActionDTOInput = MessageActionDTOInputAnyOf | null;
|
||||
|
||||
export type MessageActionDTOTooltip = string | null;
|
||||
|
||||
export type MessageActionDTOSignal = ApplyFilterSignalDTO | null;
|
||||
|
||||
export type MessageActionDTOQueryAnyOf = { [key: string]: unknown };
|
||||
|
||||
export type MessageActionDTOQuery = MessageActionDTOQueryAnyOf | null;
|
||||
|
||||
export type MessageActionDTOUrl = string | null;
|
||||
|
||||
/**
|
||||
* Assistant action. Kind-specific requirements: rollback actions require actionMetadataId/resourceType/resourceId; follow_up requires input.intent; open_resource requires resourceType/resourceId; apply_filter requires signal and query; open_docs requires a SigNoz docs url.
|
||||
*/
|
||||
export interface MessageActionDTO {
|
||||
kind: MessageActionKindDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
label: string;
|
||||
actionMetadataId?: MessageActionDTOActionMetadataId;
|
||||
resourceType?: MessageActionDTOResourceType;
|
||||
resourceId?: MessageActionDTOResourceId;
|
||||
state?: MessageActionDTOState;
|
||||
input?: MessageActionDTOInput;
|
||||
tooltip?: MessageActionDTOTooltip;
|
||||
signal?: MessageActionDTOSignal;
|
||||
query?: MessageActionDTOQuery;
|
||||
url?: MessageActionDTOUrl;
|
||||
}
|
||||
|
||||
export enum MessageActionKindDTO {
|
||||
undo = 'undo',
|
||||
revert = 'revert',
|
||||
restore = 'restore',
|
||||
follow_up = 'follow_up',
|
||||
open_resource = 'open_resource',
|
||||
open_docs = 'open_docs',
|
||||
apply_filter = 'apply_filter',
|
||||
}
|
||||
export enum MessageContentTypeDTO {
|
||||
markdown = 'markdown',
|
||||
}
|
||||
/**
|
||||
* "auto" if derived from current page; "mention" if explicitly @-picked.
|
||||
@@ -335,193 +482,6 @@ export interface MessageContextDTO {
|
||||
metadata?: MessageContextDTOMetadata;
|
||||
}
|
||||
|
||||
export type CreateMessageRequestDTOContexts = MessageContextDTO[] | null;
|
||||
|
||||
export type CreateMessageRequestDTOForkFromMessageId = string | null;
|
||||
|
||||
export interface CreateMessageRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @maxLength 20000
|
||||
* @minLength 1
|
||||
*/
|
||||
content: string;
|
||||
contexts?: CreateMessageRequestDTOContexts;
|
||||
forkFromMessageId?: CreateMessageRequestDTOForkFromMessageId;
|
||||
}
|
||||
|
||||
export interface CreateMessageResponseDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format uuid
|
||||
*/
|
||||
executionId: string;
|
||||
}
|
||||
|
||||
export type CreateThreadRequestDTOTitle = string | null;
|
||||
|
||||
export interface CreateThreadRequestDTO {
|
||||
title?: CreateThreadRequestDTOTitle;
|
||||
}
|
||||
|
||||
export interface CreateThreadResponseDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format uuid
|
||||
*/
|
||||
threadId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single sub-error entry — matches Go ErrorsResponseerroradditional.
|
||||
*/
|
||||
export interface ErrorResponseAdditionalDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type ErrorBodyDTOErrors = ErrorResponseAdditionalDTO[] | null;
|
||||
|
||||
export type ErrorBodyDTOUrl = string | null;
|
||||
|
||||
/**
|
||||
* Inner error object — matches Go ErrorsJSON.
|
||||
*/
|
||||
export interface ErrorBodyDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @pattern ^[a-z_]+$
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message: string;
|
||||
errors?: ErrorBodyDTOErrors;
|
||||
url?: ErrorBodyDTOUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level error envelope — matches Go RenderErrorResponse.
|
||||
*/
|
||||
export interface ErrorResponseDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
error: ErrorBodyDTO;
|
||||
}
|
||||
|
||||
export enum FeedbackRatingDTO {
|
||||
positive = 'positive',
|
||||
negative = 'negative',
|
||||
}
|
||||
export type FeedbackRequestDTOComment = string | null;
|
||||
|
||||
export interface FeedbackRequestDTO {
|
||||
rating: FeedbackRatingDTO;
|
||||
comment?: FeedbackRequestDTOComment;
|
||||
}
|
||||
|
||||
export interface FeedbackResponseDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type ValidationErrorDTOLocItem = string | number;
|
||||
|
||||
export type ValidationErrorDTOCtx = { [key: string]: unknown };
|
||||
|
||||
export interface ValidationErrorDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
loc: ValidationErrorDTOLocItem[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
msg: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type: string;
|
||||
input?: unknown;
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
ctx?: ValidationErrorDTOCtx;
|
||||
}
|
||||
|
||||
export interface HTTPValidationErrorDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
detail?: ValidationErrorDTO[];
|
||||
}
|
||||
|
||||
export const HealthResponseDTOValue = {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: 'ok',
|
||||
} as const;
|
||||
export type HealthResponseDTO = typeof HealthResponseDTOValue;
|
||||
|
||||
export type MessageActionDTOActionMetadataId = string | null;
|
||||
|
||||
export type MessageActionDTOResourceType = string | null;
|
||||
|
||||
export type MessageActionDTOResourceId = string | null;
|
||||
|
||||
export type MessageActionDTOState = string | null;
|
||||
|
||||
export type MessageActionDTOInputAnyOf = { [key: string]: unknown };
|
||||
|
||||
export type MessageActionDTOInput = MessageActionDTOInputAnyOf | null;
|
||||
|
||||
export type MessageActionDTOTooltip = string | null;
|
||||
|
||||
export type MessageActionDTOSignal = ApplyFilterSignalDTO | null;
|
||||
|
||||
export type MessageActionDTOQueryAnyOf = { [key: string]: unknown };
|
||||
|
||||
export type MessageActionDTOQuery = MessageActionDTOQueryAnyOf | null;
|
||||
|
||||
export type MessageActionDTOUrl = string | null;
|
||||
|
||||
export enum MessageActionKindDTO {
|
||||
undo = 'undo',
|
||||
revert = 'revert',
|
||||
restore = 'restore',
|
||||
follow_up = 'follow_up',
|
||||
open_resource = 'open_resource',
|
||||
open_docs = 'open_docs',
|
||||
apply_filter = 'apply_filter',
|
||||
}
|
||||
/**
|
||||
* Assistant action. Kind-specific requirements: rollback actions require actionMetadataId/resourceType/resourceId; follow_up requires input.intent; open_resource requires resourceType/resourceId; apply_filter requires signal and query; open_docs requires a SigNoz docs url.
|
||||
*/
|
||||
export interface MessageActionDTO {
|
||||
kind: MessageActionKindDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
label: string;
|
||||
actionMetadataId?: MessageActionDTOActionMetadataId;
|
||||
resourceType?: MessageActionDTOResourceType;
|
||||
resourceId?: MessageActionDTOResourceId;
|
||||
state?: MessageActionDTOState;
|
||||
input?: MessageActionDTOInput;
|
||||
tooltip?: MessageActionDTOTooltip;
|
||||
signal?: MessageActionDTOSignal;
|
||||
query?: MessageActionDTOQuery;
|
||||
url?: MessageActionDTOUrl;
|
||||
}
|
||||
|
||||
export enum MessageContentTypeDTO {
|
||||
markdown = 'markdown',
|
||||
}
|
||||
export enum MessageRoleDTO {
|
||||
user = 'user',
|
||||
assistant = 'assistant',
|
||||
@@ -656,10 +616,6 @@ export interface RevertRequestDTO {
|
||||
actionMetadataId: string;
|
||||
}
|
||||
|
||||
export enum ScopeDTO {
|
||||
user = 'user',
|
||||
org = 'org',
|
||||
}
|
||||
export type ThreadDetailResponseDTOTitle = string | null;
|
||||
|
||||
export type ThreadDetailResponseDTOState = ExecutionStateDTO | null;
|
||||
@@ -707,6 +663,18 @@ export interface ThreadDetailResponseDTO {
|
||||
|
||||
export type ThreadListResponseDTONextCursor = string | null;
|
||||
|
||||
export interface ThreadListResponseDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
threads: ThreadSummaryDTO[];
|
||||
nextCursor?: ThreadListResponseDTONextCursor;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
hasMore?: boolean;
|
||||
}
|
||||
|
||||
export type ThreadSummaryDTOTitle = string | null;
|
||||
|
||||
export type ThreadSummaryDTOState = ExecutionStateDTO | null;
|
||||
@@ -741,18 +709,6 @@ export interface ThreadSummaryDTO {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ThreadListResponseDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
threads: ThreadSummaryDTO[];
|
||||
nextCursor?: ThreadListResponseDTONextCursor;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
hasMore?: boolean;
|
||||
}
|
||||
|
||||
export interface UndoRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -770,29 +726,28 @@ export interface UpdateThreadRequestDTO {
|
||||
archived?: UpdateThreadRequestDTOArchived;
|
||||
}
|
||||
|
||||
export type UsageResponseDTONextPage = string | null;
|
||||
export type ValidationErrorDTOLocItem = string | number;
|
||||
|
||||
/**
|
||||
* One row in the ``GET /usage`` response.
|
||||
*/
|
||||
export interface UsageRowDTO {
|
||||
type: CounterTypeNameDTO;
|
||||
scope: ScopeDTO;
|
||||
used: number;
|
||||
limit: number;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
resetsAt: string;
|
||||
}
|
||||
export type ValidationErrorDTOCtx = { [key: string]: unknown };
|
||||
|
||||
export interface UsageResponseDTO {
|
||||
export interface ValidationErrorDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data: UsageRowDTO[];
|
||||
nextPage?: UsageResponseDTONextPage;
|
||||
loc: ValidationErrorDTOLocItem[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
msg: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type: string;
|
||||
input?: unknown;
|
||||
/**
|
||||
* @type object
|
||||
*/
|
||||
ctx?: ValidationErrorDTOCtx;
|
||||
}
|
||||
|
||||
export type ApprovalEventDTODiff = { [key: string]: unknown };
|
||||
@@ -954,20 +909,6 @@ export interface ErrorEventDTO {
|
||||
retryAction?: RetryActionDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-connection SSE keep-alive emitted every `sse_heartbeat_interval_seconds`.
|
||||
|
||||
Carries no `executionId` and no `eventId` — heartbeats are wire-level
|
||||
keep-alives, not part of the replayable event log.
|
||||
*/
|
||||
export const HeartbeatEventDTOValue = {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type: 'heartbeat',
|
||||
} as const;
|
||||
export type HeartbeatEventDTO = typeof HeartbeatEventDTOValue;
|
||||
|
||||
export type MessageActionEventDTOActionMetadataId = string | null;
|
||||
|
||||
export type MessageActionEventDTOResourceType = string | null;
|
||||
@@ -1374,14 +1315,3 @@ export type SubmitFeedbackApiV1AssistantMessagesMessageIdFeedbackPostHeaders = {
|
||||
*/
|
||||
'X-SigNoz-URL'?: string | null;
|
||||
};
|
||||
|
||||
export type GetUsageApiV1AssistantUsageGetHeaders = {
|
||||
/**
|
||||
* @description SigNoz auth token (Bearer or raw JWT)
|
||||
*/
|
||||
authorization?: string | null;
|
||||
/**
|
||||
* @description SigNoz instance base URL for multi-tenant deployments. Falls back to SIGNOZ_API_URL env var when omitted.
|
||||
*/
|
||||
'X-SigNoz-URL'?: string | null;
|
||||
};
|
||||
|
||||
@@ -18,15 +18,11 @@ import type {
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
CreateDashboardV2201,
|
||||
CreatePublicDashboard201,
|
||||
CreatePublicDashboardPathParameters,
|
||||
DashboardtypesPostableDashboardV2DTO,
|
||||
DashboardtypesPostablePublicDashboardDTO,
|
||||
DashboardtypesUpdatablePublicDashboardDTO,
|
||||
DeletePublicDashboardPathParameters,
|
||||
GetDashboardV2200,
|
||||
GetDashboardV2PathParameters,
|
||||
GetPublicDashboard200,
|
||||
GetPublicDashboardData200,
|
||||
GetPublicDashboardDataPathParameters,
|
||||
@@ -632,187 +628,3 @@ export const invalidateGetPublicDashboardWidgetQueryRange = async (
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates a dashboard in the v2 format that follows Perses spec.
|
||||
* @summary Create dashboard (v2)
|
||||
*/
|
||||
export const createDashboardV2 = (
|
||||
dashboardtypesPostableDashboardV2DTO?: BodyType<DashboardtypesPostableDashboardV2DTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateDashboardV2201>({
|
||||
url: `/api/v2/dashboards`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: dashboardtypesPostableDashboardV2DTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateDashboardV2MutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data?: BodyType<DashboardtypesPostableDashboardV2DTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data?: BodyType<DashboardtypesPostableDashboardV2DTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createDashboardV2'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
{ data?: BodyType<DashboardtypesPostableDashboardV2DTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createDashboardV2(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateDashboardV2MutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>
|
||||
>;
|
||||
export type CreateDashboardV2MutationBody =
|
||||
| BodyType<DashboardtypesPostableDashboardV2DTO>
|
||||
| undefined;
|
||||
export type CreateDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create dashboard (v2)
|
||||
*/
|
||||
export const useCreateDashboardV2 = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data?: BodyType<DashboardtypesPostableDashboardV2DTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createDashboardV2>>,
|
||||
TError,
|
||||
{ data?: BodyType<DashboardtypesPostableDashboardV2DTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getCreateDashboardV2MutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns a v2-shape dashboard.
|
||||
* @summary Get dashboard (v2)
|
||||
*/
|
||||
export const getDashboardV2 = (
|
||||
{ id }: GetDashboardV2PathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetDashboardV2200>({
|
||||
url: `/api/v2/dashboards/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetDashboardV2QueryKey = ({
|
||||
id,
|
||||
}: GetDashboardV2PathParameters) => {
|
||||
return [`/api/v2/dashboards/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetDashboardV2QueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getDashboardV2>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetDashboardV2PathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getDashboardV2>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetDashboardV2QueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getDashboardV2>>> = ({
|
||||
signal,
|
||||
}) => getDashboardV2({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getDashboardV2>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetDashboardV2QueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getDashboardV2>>
|
||||
>;
|
||||
export type GetDashboardV2QueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get dashboard (v2)
|
||||
*/
|
||||
|
||||
export function useGetDashboardV2<
|
||||
TData = Awaited<ReturnType<typeof getDashboardV2>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ id }: GetDashboardV2PathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getDashboardV2>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetDashboardV2QueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get dashboard (v2)
|
||||
*/
|
||||
export const invalidateGetDashboardV2 = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetDashboardV2PathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetDashboardV2QueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,6 @@ import type {
|
||||
import type {
|
||||
GetWaterfall200,
|
||||
GetWaterfallPathParameters,
|
||||
GetWaterfallV4200,
|
||||
GetWaterfallV4PathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
SpantypesPostableWaterfallDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
@@ -122,102 +120,3 @@ export const useGetWaterfall = <
|
||||
> => {
|
||||
return useMutation(getGetWaterfallMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* Returns the waterfall view of spans including all spans if total spans are under a limit, a max count otherwise. Aggregations are dropped compared to v3
|
||||
* @summary Get waterfall view for a trace
|
||||
*/
|
||||
export const getWaterfallV4 = (
|
||||
{ traceID }: GetWaterfallV4PathParameters,
|
||||
spantypesPostableWaterfallDTO?: BodyType<SpantypesPostableWaterfallDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetWaterfallV4200>({
|
||||
url: `/api/v4/traces/${traceID}/waterfall`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spantypesPostableWaterfallDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetWaterfallV4MutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfallV4>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallV4PathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfallV4>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallV4PathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['getWaterfallV4'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof getWaterfallV4>>,
|
||||
{
|
||||
pathParams: GetWaterfallV4PathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return getWaterfallV4(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type GetWaterfallV4MutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getWaterfallV4>>
|
||||
>;
|
||||
export type GetWaterfallV4MutationBody =
|
||||
| BodyType<SpantypesPostableWaterfallDTO>
|
||||
| undefined;
|
||||
export type GetWaterfallV4MutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get waterfall view for a trace
|
||||
*/
|
||||
export const useGetWaterfallV4 = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof getWaterfallV4>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallV4PathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof getWaterfallV4>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: GetWaterfallV4PathParameters;
|
||||
data?: BodyType<SpantypesPostableWaterfallDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getGetWaterfallV4MutationOptions(options));
|
||||
};
|
||||
|
||||
@@ -48,7 +48,6 @@ import type {
|
||||
TypesPostableInviteDTO,
|
||||
TypesPostableResetPasswordDTO,
|
||||
TypesPostableRoleDTO,
|
||||
TypesPostableVerifyResetPasswordTokenDTO,
|
||||
TypesUpdatableUserDTO,
|
||||
UpdateUserDeprecated200,
|
||||
UpdateUserDeprecatedPathParameters,
|
||||
@@ -948,90 +947,6 @@ export const useForgotPassword = <
|
||||
> => {
|
||||
return useMutation(getForgotPasswordMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint verifies whether a reset password token exists and is not expired
|
||||
* @summary Verify a reset password token
|
||||
*/
|
||||
export const verifyResetPasswordToken = (
|
||||
typesPostableVerifyResetPasswordTokenDTO?: BodyType<TypesPostableVerifyResetPasswordTokenDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/reset_password_tokens/verify`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableVerifyResetPasswordTokenDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getVerifyResetPasswordTokenMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['verifyResetPasswordToken'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
|
||||
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return verifyResetPasswordToken(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type VerifyResetPasswordTokenMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof verifyResetPasswordToken>>
|
||||
>;
|
||||
export type VerifyResetPasswordTokenMutationBody =
|
||||
| BodyType<TypesPostableVerifyResetPasswordTokenDTO>
|
||||
| undefined;
|
||||
export type VerifyResetPasswordTokenMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Verify a reset password token
|
||||
*/
|
||||
export const useVerifyResetPasswordToken = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof verifyResetPasswordToken>>,
|
||||
TError,
|
||||
{ data?: BodyType<TypesPostableVerifyResetPasswordTokenDTO> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getVerifyResetPasswordTokenMutationOptions(options));
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the users having the role by role id
|
||||
* @summary Get users by role id
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
}
|
||||
|
||||
.divider {
|
||||
--divider-color: var(--l1-border);
|
||||
--divider-margin: 10px 0 16px 0;
|
||||
border-color: var(--l1-border);
|
||||
margin: 16px 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Breadcrumb } from 'antd';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { Breadcrumb, Divider } from 'antd';
|
||||
|
||||
import styles from './AlertBreadcrumb.module.scss';
|
||||
import BreadcrumbItem, { BreadcrumbItemConfig } from './BreadcrumbItem';
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
.emptyState {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--danger-background);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-vanilla-100);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-vanilla-400);
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { LifeBuoy, RefreshCw, TriangleAlert } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import styles from './ErrorEmptyState.module.scss';
|
||||
|
||||
interface ErrorEmptyStateProps {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
|
||||
function ErrorEmptyState({
|
||||
title = 'Something went wrong',
|
||||
subtitle = 'Our team is getting on top to resolve this. Please reach out to support if the issue persists.',
|
||||
onRefresh,
|
||||
}: ErrorEmptyStateProps): JSX.Element {
|
||||
const { isCloudUser } = useGetTenantLicense();
|
||||
|
||||
const onContactSupport = useCallback((): void => {
|
||||
handleContactSupport(isCloudUser);
|
||||
}, [isCloudUser]);
|
||||
|
||||
return (
|
||||
<div className={styles.emptyState} data-testid="error-empty-state">
|
||||
<TriangleAlert className={styles.icon} size={32} />
|
||||
<div className={styles.title} data-testid="error-title">
|
||||
{title}
|
||||
</div>
|
||||
<div className={styles.subtitle} data-testid="error-subtitle">
|
||||
{subtitle}
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
prefix={<LifeBuoy size={14} />}
|
||||
onClick={onContactSupport}
|
||||
data-testid="error-contact-support-button"
|
||||
>
|
||||
Contact Support
|
||||
</Button>
|
||||
{onRefresh && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<RefreshCw size={14} />}
|
||||
onClick={onRefresh}
|
||||
data-testid="error-refresh-button"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorEmptyState;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './ErrorEmptyState';
|
||||
@@ -1,68 +0,0 @@
|
||||
.labelColumn {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.labelBadge {
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
|
||||
--badge-display: inline;
|
||||
|
||||
max-width: 180px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.overflowTrigger {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overflowBadge {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.labelPopover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.labelTooltip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.labelValue {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
|
||||
import LabelColumn from './LabelColumn';
|
||||
|
||||
let resizeCallback: ResizeObserverCallback | null = null;
|
||||
|
||||
class MockResizeObserver {
|
||||
constructor(callback: ResizeObserverCallback) {
|
||||
resizeCallback = callback;
|
||||
}
|
||||
|
||||
observe = jest.fn();
|
||||
unobserve = jest.fn();
|
||||
disconnect = jest.fn();
|
||||
}
|
||||
|
||||
function triggerResize(width: number): void {
|
||||
if (resizeCallback) {
|
||||
act(() => {
|
||||
resizeCallback?.(
|
||||
[{ contentRect: { width } } as ResizeObserverEntry],
|
||||
{} as ResizeObserver,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
global.ResizeObserver = MockResizeObserver as unknown as typeof ResizeObserver;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resizeCallback = null;
|
||||
});
|
||||
|
||||
function renderWithProviders(
|
||||
ui: React.ReactElement,
|
||||
): ReturnType<typeof render> {
|
||||
return render(<TooltipProvider>{ui}</TooltipProvider>);
|
||||
}
|
||||
|
||||
describe('LabelColumn', () => {
|
||||
it('should render all labels when 5 or fewer', () => {
|
||||
const labels = ['env', 'service', 'region'];
|
||||
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
expect(screen.getByTestId('label-tag-env')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('label-tag-service')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('label-tag-region')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should truncate labels and show +N badge when container is narrow', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
// Simulate narrow container that fits ~3 badges
|
||||
// Badge widths: env=37, service=65, region=58, team=44, owner=51, version=65
|
||||
// 220px available = 3 badges (160px) + gaps (8px) + overflow (44px)
|
||||
triggerResize(220);
|
||||
|
||||
// First 3 visible
|
||||
expect(screen.getByTestId('label-tag-env')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('label-tag-service')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('label-tag-region')).toBeInTheDocument();
|
||||
|
||||
// Remaining in overflow badge
|
||||
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+3');
|
||||
});
|
||||
|
||||
it('should render label with value when value prop provided', () => {
|
||||
const labels = ['env'];
|
||||
const value = { env: 'production' };
|
||||
|
||||
renderWithProviders(<LabelColumn labels={labels} value={value} />);
|
||||
|
||||
expect(screen.getByTestId('label-tag-env')).toHaveTextContent(
|
||||
'env: production',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render labels without value when value is not provided for that label', () => {
|
||||
const labels = ['env', 'service'];
|
||||
const value = { env: 'production' };
|
||||
|
||||
renderWithProviders(<LabelColumn labels={labels} value={value} />);
|
||||
|
||||
expect(screen.getByTestId('label-tag-env')).toHaveTextContent(
|
||||
'env: production',
|
||||
);
|
||||
expect(screen.getByTestId('label-tag-service')).toHaveTextContent('service');
|
||||
});
|
||||
|
||||
it('should show overflow badge with remaining count when container is narrow', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
// Simulate narrow container to trigger overflow (shows 3 labels)
|
||||
// 220px fits first 3 badges before overflow
|
||||
triggerResize(220);
|
||||
|
||||
// Overflow badge shows +3 (remaining labels)
|
||||
const overflowBadge = screen.getByTestId('label-overflow-badge');
|
||||
expect(overflowBadge).toBeInTheDocument();
|
||||
expect(overflowBadge).toHaveTextContent('+3');
|
||||
});
|
||||
|
||||
it('should render empty when no labels provided', () => {
|
||||
renderWithProviders(<LabelColumn labels={[]} />);
|
||||
|
||||
const column = screen.getByTestId('label-column');
|
||||
expect(column.children).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should use primary color by default', () => {
|
||||
const labels = ['env'];
|
||||
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
expect(screen.getByTestId('label-tag-env')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show all labels when container is wide enough', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
// Simulate wide container
|
||||
triggerResize(1000);
|
||||
|
||||
// All labels visible
|
||||
labels.forEach((label) => {
|
||||
expect(screen.getByTestId(`label-tag-${label}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// No overflow badge
|
||||
expect(screen.queryByTestId('label-overflow-badge')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
import { Copy } from '@signozhq/icons';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
} from '@signozhq/ui/tooltip';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import LabelTag from './LabelTag';
|
||||
|
||||
import styles from './LabelColumn.module.scss';
|
||||
import { BADGE_GAP, estimateBadgeWidth, OVERFLOW_BADGE_WIDTH } from './utils';
|
||||
|
||||
export interface LabelColumnProps {
|
||||
labels: string[];
|
||||
color?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'success'
|
||||
| 'error'
|
||||
| 'warning'
|
||||
| 'robin'
|
||||
| 'forest'
|
||||
| 'amber'
|
||||
| 'sienna'
|
||||
| 'cherry'
|
||||
| 'sakura'
|
||||
| 'aqua'
|
||||
| 'vanilla';
|
||||
value?: { [key: string]: string };
|
||||
}
|
||||
|
||||
function LabelColumn({
|
||||
labels,
|
||||
value,
|
||||
color = 'primary',
|
||||
}: LabelColumnProps): JSX.Element {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [maxVisibleCount, setMaxVisibleCount] = useState(labels.length);
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const calculateMaxVisible = useCallback(
|
||||
(width: number): number => {
|
||||
if (width <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const availableWidth = width - OVERFLOW_BADGE_WIDTH - BADGE_GAP;
|
||||
let usedWidth = 0;
|
||||
let count = 0;
|
||||
|
||||
for (const label of labels) {
|
||||
const badgeWidth = estimateBadgeWidth(label, value?.[label]) + BADGE_GAP;
|
||||
if (usedWidth + badgeWidth > availableWidth && count > 0) {
|
||||
break;
|
||||
}
|
||||
usedWidth += badgeWidth;
|
||||
count++;
|
||||
}
|
||||
|
||||
return Math.max(1, count);
|
||||
},
|
||||
[labels, value],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (entry && entry.contentRect.width > 0) {
|
||||
setMaxVisibleCount(calculateMaxVisible(entry.contentRect.width));
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(container);
|
||||
|
||||
if (container.clientWidth > 0) {
|
||||
setMaxVisibleCount(calculateMaxVisible(container.clientWidth));
|
||||
}
|
||||
|
||||
return (): void => observer.disconnect();
|
||||
}, [calculateMaxVisible]);
|
||||
|
||||
const needsOverflow = labels.length > maxVisibleCount;
|
||||
const visibleLabels = needsOverflow
|
||||
? labels.slice(0, maxVisibleCount)
|
||||
: labels;
|
||||
const remainingLabels = needsOverflow ? labels.slice(maxVisibleCount) : [];
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={styles.labelColumn}
|
||||
data-testid="label-column"
|
||||
>
|
||||
{visibleLabels.map((label) => (
|
||||
<LabelTag key={label} label={label} color={color} value={value?.[label]} />
|
||||
))}
|
||||
{remainingLabels.length > 0 && (
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Badge
|
||||
color={color}
|
||||
className={styles.overflowBadge}
|
||||
variant="outline"
|
||||
data-testid="label-overflow-badge"
|
||||
>
|
||||
+{remainingLabels.length}
|
||||
</Badge>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="end">
|
||||
<div className={styles.tooltipContent}>
|
||||
<span>
|
||||
{remainingLabels
|
||||
.map((label) => (value?.[label] ? `${label}: ${value[label]}` : label))
|
||||
.join(', ')}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.copyButton}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
const searchFormat = remainingLabels
|
||||
.map((label) => (value?.[label] ? `${label} ${value[label]}` : label))
|
||||
.join(' ');
|
||||
copyToClipboard(searchFormat);
|
||||
toast.success('Copied! Use in search to filter alerts.');
|
||||
}}
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<Copy size={12} />
|
||||
</button>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</TooltipRoot>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LabelColumn;
|
||||
@@ -1,30 +0,0 @@
|
||||
.labelBadge {
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
|
||||
max-width: 180px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.labelValue {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { Copy } from '@signozhq/icons';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
} from '@signozhq/ui/tooltip';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
import styles from './LabelTag.module.scss';
|
||||
|
||||
export interface LabelTagProps {
|
||||
label: string;
|
||||
color?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'success'
|
||||
| 'error'
|
||||
| 'warning'
|
||||
| 'robin'
|
||||
| 'forest'
|
||||
| 'amber'
|
||||
| 'sienna'
|
||||
| 'cherry'
|
||||
| 'sakura'
|
||||
| 'aqua'
|
||||
| 'vanilla';
|
||||
value?: string;
|
||||
}
|
||||
|
||||
function LabelTag({ label, value, color }: LabelTagProps): JSX.Element {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const displayText = value ? `${label}: ${value}` : label;
|
||||
const searchFormat = value ? `${label} ${value}` : label;
|
||||
|
||||
const handleCopy = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(searchFormat);
|
||||
toast.success('Copied! Use in search to filter alerts.');
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Badge
|
||||
color={color}
|
||||
className={styles.labelBadge}
|
||||
variant="outline"
|
||||
data-testid={`label-tag-${label}`}
|
||||
>
|
||||
<span className={styles.labelValue}>{displayText}</span>
|
||||
</Badge>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className={styles.tooltipContent}>
|
||||
<span>{displayText}</span>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.copyButton}
|
||||
onClick={handleCopy}
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<Copy size={12} />
|
||||
</button>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</TooltipRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export default LabelTag;
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from './LabelColumn';
|
||||
export type { LabelColumnProps } from './LabelColumn';
|
||||
@@ -1,14 +0,0 @@
|
||||
export const BADGE_GAP = 4;
|
||||
export const OVERFLOW_BADGE_WIDTH = 40;
|
||||
|
||||
export const BADGE_MAX_WIDTH = 180;
|
||||
export const BADGE_PADDING = 16;
|
||||
export const CHAR_WIDTH = 7;
|
||||
|
||||
export function estimateBadgeWidth(label: string, value?: string): number {
|
||||
const displayText = value ? `${label}: ${value}` : label;
|
||||
return Math.min(
|
||||
displayText.length * CHAR_WIDTH + BADGE_PADDING,
|
||||
BADGE_MAX_WIDTH,
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
.emptyState {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--text-vanilla-400);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-vanilla-100);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-vanilla-400);
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import NoResultsEmptyState from './NoResultsEmptyState';
|
||||
|
||||
describe('NoResultsEmptyState', () => {
|
||||
it('should render with default props', () => {
|
||||
render(<NoResultsEmptyState />);
|
||||
|
||||
expect(screen.getByTestId('no-results-empty-state')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('no-results-title')).toHaveTextContent(
|
||||
'No matching results',
|
||||
);
|
||||
expect(screen.getByTestId('no-results-subtitle')).toHaveTextContent(
|
||||
'No items match your current filters. Try adjusting your search criteria.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with custom title and subtitle', () => {
|
||||
render(
|
||||
<NoResultsEmptyState title="Custom Title" subtitle="Custom Subtitle" />,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('no-results-title')).toHaveTextContent(
|
||||
'Custom Title',
|
||||
);
|
||||
expect(screen.getByTestId('no-results-subtitle')).toHaveTextContent(
|
||||
'Custom Subtitle',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render clear button when onClear is not provided', () => {
|
||||
render(<NoResultsEmptyState />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('no-results-clear-button'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render clear button when onClear is provided', () => {
|
||||
const onClear = jest.fn();
|
||||
|
||||
render(<NoResultsEmptyState onClear={onClear} />);
|
||||
|
||||
expect(screen.getByTestId('no-results-clear-button')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('no-results-clear-button')).toHaveTextContent(
|
||||
'Clear Filters',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render custom clear button text', () => {
|
||||
render(
|
||||
<NoResultsEmptyState onClear={jest.fn()} clearButtonText="Reset All" />,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('no-results-clear-button')).toHaveTextContent(
|
||||
'Reset All',
|
||||
);
|
||||
});
|
||||
|
||||
it('should call onClear when clear button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClear = jest.fn();
|
||||
|
||||
render(<NoResultsEmptyState onClear={onClear} />);
|
||||
|
||||
await user.click(screen.getByTestId('no-results-clear-button'));
|
||||
|
||||
expect(onClear).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import { RefreshCw, Search } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
|
||||
import styles from './NoResultsEmptyState.module.scss';
|
||||
|
||||
interface NoResultsEmptyStateProps {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
onClear?: () => void;
|
||||
clearButtonText?: string;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
|
||||
function NoResultsEmptyState({
|
||||
title = 'No matching results',
|
||||
subtitle = 'No items match your current filters. Try adjusting your search criteria.',
|
||||
onClear,
|
||||
clearButtonText = 'Clear Filters',
|
||||
onRefresh,
|
||||
}: NoResultsEmptyStateProps): JSX.Element {
|
||||
return (
|
||||
<div className={styles.emptyState} data-testid="no-results-empty-state">
|
||||
<Search className={styles.icon} size={16} />
|
||||
<div className={styles.title} data-testid="no-results-title">
|
||||
{title}
|
||||
</div>
|
||||
<div className={styles.subtitle} data-testid="no-results-subtitle">
|
||||
{subtitle}
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
{onClear && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={onClear}
|
||||
data-testid="no-results-clear-button"
|
||||
>
|
||||
{clearButtonText}
|
||||
</Button>
|
||||
)}
|
||||
{onRefresh && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prefix={<RefreshCw size={14} />}
|
||||
onClick={onRefresh}
|
||||
data-testid="no-results-refresh-button"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoResultsEmptyState;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './NoResultsEmptyState';
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { BadgeColor } from '@signozhq/ui/badge';
|
||||
|
||||
export const STATE_ORDER = ['firing', 'pending', 'inactive', 'disabled'];
|
||||
export const SEVERITY_ORDER = ['critical', 'error', 'warning', 'info'];
|
||||
|
||||
export const STATE_LABELS: Record<string, string> = {
|
||||
firing: 'Firing',
|
||||
pending: 'Pending',
|
||||
inactive: 'OK',
|
||||
disabled: 'Disabled',
|
||||
};
|
||||
|
||||
export const STATE_COLORS: Record<string, string> = {
|
||||
firing: 'var(--bg-cherry-500)',
|
||||
pending: 'var(--bg-amber-500)',
|
||||
inactive: 'var(--bg-forest-500)',
|
||||
disabled: 'var(--l2-foreground)',
|
||||
};
|
||||
|
||||
export const SEVERITY_COLORS: Record<string, string> = {
|
||||
critical: 'var(--bg-cherry-500)',
|
||||
error: 'var(--bg-cherry-400)',
|
||||
warning: 'var(--bg-amber-500)',
|
||||
info: 'var(--bg-robin-500)',
|
||||
};
|
||||
|
||||
export const SEVERITY_BADGE_COLORS: Record<string, BadgeColor> = {
|
||||
critical: 'error',
|
||||
error: 'error',
|
||||
warning: 'warning',
|
||||
info: 'primary',
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface FilterValue {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface AlertWithLabels {
|
||||
labels?: Record<string, string>;
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
import type { SortState } from 'components/TanStackTableView/types';
|
||||
|
||||
import type { AlertWithLabels, FilterValue } from './types';
|
||||
import { filterByLabels, searchByLabels, sortByColumn } from './utils';
|
||||
|
||||
interface TestAlert extends AlertWithLabels {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
const createAlert = (
|
||||
name: string,
|
||||
value: number,
|
||||
labels?: Record<string, string>,
|
||||
): TestAlert => ({
|
||||
name,
|
||||
value,
|
||||
labels,
|
||||
});
|
||||
|
||||
describe('sortByColumn', () => {
|
||||
const alerts: TestAlert[] = [
|
||||
createAlert('Alert C', 3),
|
||||
createAlert('Alert A', 1),
|
||||
createAlert('Alert B', 2),
|
||||
];
|
||||
|
||||
const getSortValue = (
|
||||
item: TestAlert,
|
||||
columnName: string,
|
||||
): string | number => {
|
||||
if (columnName === 'name') {
|
||||
return item.name;
|
||||
}
|
||||
if (columnName === 'value') {
|
||||
return item.value;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
it('should return items unchanged when no orderBy provided', () => {
|
||||
const result = sortByColumn(alerts, null, getSortValue);
|
||||
expect(result).toStrictEqual(alerts);
|
||||
});
|
||||
|
||||
it('should sort by string column ascending', () => {
|
||||
const orderBy: SortState = { columnName: 'name', order: 'asc' };
|
||||
const result = sortByColumn(alerts, orderBy, getSortValue);
|
||||
expect(result.map((a) => a.name)).toStrictEqual([
|
||||
'Alert A',
|
||||
'Alert B',
|
||||
'Alert C',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort by string column descending', () => {
|
||||
const orderBy: SortState = { columnName: 'name', order: 'desc' };
|
||||
const result = sortByColumn(alerts, orderBy, getSortValue);
|
||||
expect(result.map((a) => a.name)).toStrictEqual([
|
||||
'Alert C',
|
||||
'Alert B',
|
||||
'Alert A',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort by number column ascending', () => {
|
||||
const orderBy: SortState = { columnName: 'value', order: 'asc' };
|
||||
const result = sortByColumn(alerts, orderBy, getSortValue);
|
||||
expect(result.map((a) => a.value)).toStrictEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should sort by number column descending', () => {
|
||||
const orderBy: SortState = { columnName: 'value', order: 'desc' };
|
||||
const result = sortByColumn(alerts, orderBy, getSortValue);
|
||||
expect(result.map((a) => a.value)).toStrictEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
it('should use defaultSort when orderBy is null', () => {
|
||||
const defaultSort: SortState = { columnName: 'value', order: 'asc' };
|
||||
const result = sortByColumn(alerts, null, getSortValue, defaultSort);
|
||||
expect(result.map((a) => a.value)).toStrictEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should not mutate original array', () => {
|
||||
const original = [...alerts];
|
||||
const orderBy: SortState = { columnName: 'name', order: 'asc' };
|
||||
sortByColumn(alerts, orderBy, getSortValue);
|
||||
expect(alerts).toStrictEqual(original);
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
const result = sortByColumn(
|
||||
[],
|
||||
{ columnName: 'name', order: 'asc' },
|
||||
getSortValue,
|
||||
);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle equal values', () => {
|
||||
const duplicates = [
|
||||
createAlert('Same', 1),
|
||||
createAlert('Same', 1),
|
||||
createAlert('Same', 1),
|
||||
];
|
||||
const orderBy: SortState = { columnName: 'name', order: 'asc' };
|
||||
const result = sortByColumn(duplicates, orderBy, getSortValue);
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByLabels', () => {
|
||||
const alerts: TestAlert[] = [
|
||||
createAlert('CPU High', 1, { severity: 'critical', team: 'infra' }),
|
||||
createAlert('Memory Warning', 2, { severity: 'warning', team: 'backend' }),
|
||||
createAlert('Disk Full', 3, { severity: 'error', region: 'us-east' }),
|
||||
createAlert('Network Slow', 4, {}),
|
||||
createAlert('No Labels', 5),
|
||||
];
|
||||
|
||||
const getAlertName = (alert: TestAlert): string => alert.name;
|
||||
|
||||
it('should return all items when search is empty', () => {
|
||||
const result = searchByLabels(alerts, '', getAlertName);
|
||||
expect(result).toStrictEqual(alerts);
|
||||
});
|
||||
|
||||
it('should return all items when search is whitespace', () => {
|
||||
const result = searchByLabels(alerts, ' ', getAlertName);
|
||||
expect(result).toStrictEqual(alerts);
|
||||
});
|
||||
|
||||
it('should search by alert name', () => {
|
||||
const result = searchByLabels(alerts, 'CPU', getAlertName);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('CPU High');
|
||||
});
|
||||
|
||||
it('should search by alert name case-insensitive', () => {
|
||||
const result = searchByLabels(alerts, 'cpu', getAlertName);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('CPU High');
|
||||
});
|
||||
|
||||
it('should search by severity label', () => {
|
||||
const result = searchByLabels(alerts, 'critical', getAlertName);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('CPU High');
|
||||
});
|
||||
|
||||
it('should search by any label key', () => {
|
||||
const result = searchByLabels(alerts, 'team', getAlertName);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should search by any label value', () => {
|
||||
const result = searchByLabels(alerts, 'infra', getAlertName);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('CPU High');
|
||||
});
|
||||
|
||||
it('should handle alerts with no labels', () => {
|
||||
const result = searchByLabels(alerts, 'No Labels', getAlertName);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('No Labels');
|
||||
});
|
||||
|
||||
it('should handle partial matches', () => {
|
||||
const result = searchByLabels(alerts, 'warn', getAlertName);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('Memory Warning');
|
||||
});
|
||||
|
||||
it('should return empty for no matches', () => {
|
||||
const result = searchByLabels(alerts, 'nonexistent', getAlertName);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should trim search text', () => {
|
||||
const result = searchByLabels(alerts, ' CPU ', getAlertName);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('CPU High');
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterByLabels', () => {
|
||||
const alerts: TestAlert[] = [
|
||||
createAlert('A1', 1, { severity: 'critical', team: 'infra', env: 'prod' }),
|
||||
createAlert('A2', 2, { severity: 'critical', team: 'backend', env: 'prod' }),
|
||||
createAlert('A3', 3, { severity: 'warning', team: 'infra', env: 'staging' }),
|
||||
createAlert('A4', 4, { severity: 'info', team: 'frontend', env: 'dev' }),
|
||||
createAlert('A5', 5, {}),
|
||||
createAlert('A6', 6),
|
||||
];
|
||||
|
||||
const createFilter = (value: string): FilterValue => ({ value });
|
||||
|
||||
it('should return all items when filters are empty', () => {
|
||||
const result = filterByLabels(alerts, []);
|
||||
expect(result).toStrictEqual(alerts);
|
||||
});
|
||||
|
||||
it('should return all items when filters is null-ish', () => {
|
||||
const result = filterByLabels(alerts, null as unknown as FilterValue[]);
|
||||
expect(result).toStrictEqual(alerts);
|
||||
});
|
||||
|
||||
it('should filter by single label', () => {
|
||||
const filters = [createFilter('severity:critical')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result.map((a) => a.name)).toStrictEqual(['A1', 'A2']);
|
||||
});
|
||||
|
||||
it('should use OR logic for same key', () => {
|
||||
const filters = [
|
||||
createFilter('severity:critical'),
|
||||
createFilter('severity:warning'),
|
||||
];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result.map((a) => a.name)).toStrictEqual(['A1', 'A2', 'A3']);
|
||||
});
|
||||
|
||||
it('should use AND logic for different keys', () => {
|
||||
const filters = [
|
||||
createFilter('severity:critical'),
|
||||
createFilter('team:infra'),
|
||||
];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('A1');
|
||||
});
|
||||
|
||||
it('should handle case-insensitive keys', () => {
|
||||
const filters = [createFilter('SEVERITY:critical')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should handle case-insensitive values', () => {
|
||||
const filters = [createFilter('severity:CRITICAL')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should trim whitespace', () => {
|
||||
const filters = [createFilter(' severity : critical ')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should return empty for invalid filter format', () => {
|
||||
const filters = [createFilter('invalid')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should ignore invalid filters mixed with valid', () => {
|
||||
const filters = [createFilter('invalid'), createFilter('severity:critical')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should exclude alerts without matching label key', () => {
|
||||
const filters = [createFilter('nonexistent:value')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should exclude alerts with no labels', () => {
|
||||
const filters = [createFilter('severity:critical')];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result.every((a) => a.labels !== undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle complex AND/OR combinations', () => {
|
||||
const filters = [
|
||||
createFilter('env:prod'),
|
||||
createFilter('env:staging'),
|
||||
createFilter('team:infra'),
|
||||
];
|
||||
const result = filterByLabels(alerts, filters);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result.map((a) => a.name)).toStrictEqual(['A1', 'A3']);
|
||||
});
|
||||
});
|
||||
@@ -1,116 +0,0 @@
|
||||
import type { SortState } from 'components/TanStackTableView/types';
|
||||
|
||||
import type { AlertWithLabels, FilterValue } from './types';
|
||||
|
||||
/**
|
||||
* Generic sort function for alert-like data
|
||||
*/
|
||||
export function sortByColumn<T>(
|
||||
items: T[],
|
||||
orderBy: SortState | null,
|
||||
getSortValue: (item: T, columnName: string) => string | number,
|
||||
defaultSort?: SortState,
|
||||
): T[] {
|
||||
const sortState = orderBy ?? defaultSort;
|
||||
if (!sortState) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const { columnName, order } = sortState;
|
||||
const multiplier = order === 'asc' ? 1 : -1;
|
||||
|
||||
return [...items].sort((a, b) => {
|
||||
const aVal = getSortValue(a, columnName);
|
||||
const bVal = getSortValue(b, columnName);
|
||||
|
||||
if (aVal < bVal) {
|
||||
return -1 * multiplier;
|
||||
}
|
||||
if (aVal > bVal) {
|
||||
return 1 * multiplier;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search alerts/rules by name, severity, and all labels
|
||||
*/
|
||||
export function searchByLabels<T extends AlertWithLabels>(
|
||||
items: T[],
|
||||
searchText: string,
|
||||
getAlertName: (item: T) => string,
|
||||
): T[] {
|
||||
if (!searchText.trim()) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const value = searchText.toLowerCase().trim();
|
||||
|
||||
return items.filter((item) => {
|
||||
const alertName = getAlertName(item).toLowerCase();
|
||||
const severity = item.labels?.severity?.toLowerCase() ?? '';
|
||||
|
||||
const labelSearchString = Object.entries(item.labels ?? {})
|
||||
.map(([key, val]) => `${key} ${val}`)
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
|
||||
return (
|
||||
alertName.includes(value) ||
|
||||
severity.includes(value) ||
|
||||
labelSearchString.includes(value)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter alerts by label key:value pairs
|
||||
* Same key uses OR logic, different keys use AND logic
|
||||
*/
|
||||
export function filterByLabels<T extends AlertWithLabels>(
|
||||
items: T[],
|
||||
selectedFilters: FilterValue[],
|
||||
): T[] {
|
||||
if (!selectedFilters?.length) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const validFilters = selectedFilters
|
||||
.map((e) => e.value)
|
||||
.filter((v) => v.split(':').length === 2);
|
||||
|
||||
if (!validFilters.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Group values by key - same key uses OR, different keys use AND
|
||||
const filtersByKey = new Map<string, string[]>();
|
||||
validFilters.forEach((f) => {
|
||||
const [key, value] = f.split(':');
|
||||
const trimmedKey = key.trim().toLowerCase();
|
||||
const trimmedValue = value.trim().toLowerCase();
|
||||
const existing = filtersByKey.get(trimmedKey) ?? [];
|
||||
existing.push(trimmedValue);
|
||||
filtersByKey.set(trimmedKey, existing);
|
||||
});
|
||||
|
||||
return items.filter((item) => {
|
||||
if (!item.labels) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All keys must match (AND), any value per key can match (OR)
|
||||
return Array.from(filtersByKey.entries()).every(([filterKey, values]) => {
|
||||
// Case-insensitive key lookup
|
||||
const matchingKey = Object.keys(item.labels ?? {}).find(
|
||||
(k) => k.toLowerCase() === filterKey,
|
||||
);
|
||||
if (!matchingKey) {
|
||||
return false;
|
||||
}
|
||||
const labelValue = item.labels?.[matchingKey]?.toLowerCase();
|
||||
return values.some((v) => labelValue === v);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Drawer } from 'antd';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { Divider, Drawer } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Select, Tooltip } from 'antd';
|
||||
import { Select, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
OPERATORS,
|
||||
QUERY_BUILDER_OPERATORS_BY_TYPES,
|
||||
@@ -51,7 +51,6 @@ import { popupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import './ClientSideQBSearch.styles.scss';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
|
||||
export interface AttributeKey {
|
||||
key: string;
|
||||
@@ -548,14 +547,10 @@ function ClientSideQBSearch(
|
||||
|
||||
return (
|
||||
<span className="qb-search-bar-tokenised-tags">
|
||||
<Badge
|
||||
color="vanilla"
|
||||
className={tagDetails?.key?.type || ''}
|
||||
<Tag
|
||||
closable={!searchValue && closable}
|
||||
onClose={(e): void => {
|
||||
e.preventDefault();
|
||||
onCloseHandler();
|
||||
}}
|
||||
onClose={onCloseHandler}
|
||||
className={tagDetails?.key?.type || ''}
|
||||
>
|
||||
<Tooltip title={chipValue}>
|
||||
<TypographyText
|
||||
@@ -571,7 +566,7 @@ function ClientSideQBSearch(
|
||||
{chipValue}
|
||||
</TypographyText>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
</Tag>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,22 +41,14 @@ $item-spacing: 8px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
height: auto;
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
padding: 0;
|
||||
|
||||
&:focus,
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
border: none;
|
||||
&.ant-input:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Input } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Button, Popover, Tooltip } from 'antd';
|
||||
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
|
||||
import { Button, Popover, Radio, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
|
||||
@@ -64,30 +63,27 @@ export default function DownloadOptionsMenu({
|
||||
>
|
||||
<div className="export-format">
|
||||
<Typography.Text className="title">FORMAT</Typography.Text>
|
||||
<RadioGroup value={exportFormat} onChange={setExportFormat}>
|
||||
<RadioGroupItem value={DownloadFormats.CSV}>csv</RadioGroupItem>
|
||||
<RadioGroupItem value={DownloadFormats.JSONL}>jsonl</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
<Radio.Group
|
||||
value={exportFormat}
|
||||
onChange={(e): void => setExportFormat(e.target.value)}
|
||||
>
|
||||
<Radio value={DownloadFormats.CSV}>csv</Radio>
|
||||
<Radio value={DownloadFormats.JSONL}>jsonl</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
<div className="horizontal-line" />
|
||||
|
||||
<div className="row-limit">
|
||||
<Typography.Text className="title">Number of Rows</Typography.Text>
|
||||
<RadioGroup
|
||||
value={String(rowLimit)}
|
||||
onChange={(value): void => setRowLimit(Number(value))}
|
||||
<Radio.Group
|
||||
value={rowLimit}
|
||||
onChange={(e): void => setRowLimit(e.target.value)}
|
||||
>
|
||||
<RadioGroupItem value={String(DownloadRowCounts.TEN_K)}>
|
||||
10k
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={String(DownloadRowCounts.THIRTY_K)}>
|
||||
30k
|
||||
</RadioGroupItem>
|
||||
<RadioGroupItem value={String(DownloadRowCounts.FIFTY_K)}>
|
||||
50k
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
<Radio value={DownloadRowCounts.TEN_K}>10k</Radio>
|
||||
<Radio value={DownloadRowCounts.THIRTY_K}>30k</Radio>
|
||||
<Radio value={DownloadRowCounts.FIFTY_K}>50k</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
{dataSource !== DataSource.TRACES && (
|
||||
@@ -96,12 +92,13 @@ export default function DownloadOptionsMenu({
|
||||
|
||||
<div className="columns-scope">
|
||||
<Typography.Text className="title">Columns</Typography.Text>
|
||||
<RadioGroup value={columnsScope} onChange={setColumnsScope}>
|
||||
<RadioGroupItem value={DownloadColumnsScopes.ALL}>All</RadioGroupItem>
|
||||
<RadioGroupItem value={DownloadColumnsScopes.SELECTED}>
|
||||
Selected
|
||||
</RadioGroupItem>
|
||||
</RadioGroup>
|
||||
<Radio.Group
|
||||
value={columnsScope}
|
||||
onChange={(e): void => setColumnsScope(e.target.value)}
|
||||
>
|
||||
<Radio value={DownloadColumnsScopes.ALL}>All</Radio>
|
||||
<Radio value={DownloadColumnsScopes.SELECTED}>Selected</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
7
frontend/src/components/DropDown/DropDown.styles.scss
Normal file
7
frontend/src/components/DropDown/DropDown.styles.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.dropdown-button {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
51
frontend/src/components/DropDown/DropDown.tsx
Normal file
51
frontend/src/components/DropDown/DropDown.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useState } from 'react';
|
||||
import { Ellipsis } from '@signozhq/icons';
|
||||
import { Button, Dropdown, MenuProps } from 'antd';
|
||||
|
||||
import './DropDown.styles.scss';
|
||||
|
||||
function DropDown({
|
||||
element,
|
||||
onDropDownItemClick,
|
||||
}: {
|
||||
element: JSX.Element[];
|
||||
onDropDownItemClick?: MenuProps['onClick'];
|
||||
}): JSX.Element {
|
||||
const items: MenuProps['items'] = element.map(
|
||||
(e: JSX.Element, index: number) => ({
|
||||
label: e,
|
||||
key: index,
|
||||
}),
|
||||
);
|
||||
|
||||
const [isDdOpen, setDdOpen] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items,
|
||||
onMouseEnter: (): void => setDdOpen(true),
|
||||
onMouseLeave: (): void => setDdOpen(false),
|
||||
onClick: (item): void => onDropDownItemClick?.(item),
|
||||
}}
|
||||
open={isDdOpen}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
className={`dropdown-button`}
|
||||
onClick={(e): void => {
|
||||
e.preventDefault();
|
||||
setDdOpen(true);
|
||||
}}
|
||||
>
|
||||
<Ellipsis className="dropdown-icon" size={16} />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
DropDown.defaultProps = {
|
||||
onDropDownItemClick: (): void => {},
|
||||
};
|
||||
|
||||
export default DropDown;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { Button, Modal, Tag } from 'antd';
|
||||
import { CircleAlert, X } from '@signozhq/icons';
|
||||
import KeyValueLabel from 'periscope/components/KeyValueLabel';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@@ -9,7 +9,6 @@ import APIError from 'types/api/error';
|
||||
import ErrorContent from './components/ErrorContent';
|
||||
|
||||
import './ErrorModal.styles.scss';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
|
||||
type Props = {
|
||||
error: APIError;
|
||||
@@ -46,17 +45,14 @@ function ErrorModal({
|
||||
return (
|
||||
<>
|
||||
{!triggerComponent ? (
|
||||
<span
|
||||
<Tag
|
||||
className="error-modal__trigger"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
icon={<CircleAlert size={14} color={Color.BG_CHERRY_500} />}
|
||||
color="error"
|
||||
onClick={(): void => setVisible(true)}
|
||||
onKeyDown={undefined}
|
||||
>
|
||||
<Badge color="error">
|
||||
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
|
||||
</Badge>
|
||||
</span>
|
||||
error
|
||||
</Tag>
|
||||
) : (
|
||||
React.cloneElement(triggerComponent, {
|
||||
onClick: () => setVisible(true),
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Button, Col, Popover, Row, Select, Space } from 'antd';
|
||||
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Dropdown,
|
||||
MenuProps,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
} from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import axios from 'axios';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
@@ -233,9 +241,9 @@ function ExplorerCard({
|
||||
</Popover>
|
||||
<Share2 onClick={onCopyUrlHandler} size="md" />
|
||||
{viewKey && (
|
||||
<DropdownMenuSimple menu={moreOptionMenu}>
|
||||
<Button type="text" size="small" icon={<Ellipsis size="md" />} />
|
||||
</DropdownMenuSimple>
|
||||
<Dropdown trigger={['click']} menu={moreOptionMenu}>
|
||||
<Ellipsis size="md" />
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
</OffSetCol>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Card, Form } from 'antd';
|
||||
import { Card, Form, Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Button, Input } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Button, Input, Radio, RadioChangeEvent } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
@@ -102,12 +101,13 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
return (
|
||||
<div className="feedback-modal-container">
|
||||
<div className="feedback-modal-header">
|
||||
<ToggleGroupSimple
|
||||
type="single"
|
||||
<Radio.Group
|
||||
value={activeTab}
|
||||
defaultValue={activeTab}
|
||||
optionType="button"
|
||||
className="feedback-modal-tabs"
|
||||
onChange={setActiveTab}
|
||||
items={items}
|
||||
options={items}
|
||||
onChange={(e: RadioChangeEvent): void => setActiveTab(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="feedback-modal-content">
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dot } from '@signozhq/icons';
|
||||
import { Dot, Sparkles } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import Noz from 'components/Noz/Noz';
|
||||
import { Popover } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { AIAssistantEvents } from 'container/AIAssistant/events';
|
||||
@@ -22,7 +21,6 @@ import FeedbackModal from './FeedbackModal';
|
||||
import ShareURLModal from './ShareURLModal';
|
||||
|
||||
import './HeaderRightSection.styles.scss';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
interface HeaderRightSectionProps {
|
||||
enableAnnouncements: boolean;
|
||||
@@ -109,22 +107,21 @@ function HeaderRightSection({
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
<TooltipSimple title="Noz">
|
||||
<TooltipSimple title="AI Assistant">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
className="noz-wave"
|
||||
onClick={handleOpenAIAssistant}
|
||||
aria-label={
|
||||
showHeaderPendingBadge
|
||||
? pendingUserInputCount === 1
|
||||
? 'Open Noz, 1 action needs your response'
|
||||
: `Open Noz, ${pendingUserInputCount} actions need your response`
|
||||
: 'Open Noz'
|
||||
? 'Open AI Assistant, 1 action needs your response'
|
||||
: `Open AI Assistant, ${pendingUserInputCount} actions need your response`
|
||||
: 'Open AI Assistant'
|
||||
}
|
||||
prefix={<Noz size={20} />}
|
||||
prefix={<Sparkles size={14} color="var(--primary)" />}
|
||||
>
|
||||
<Typography.Text>Noz</Typography.Text>
|
||||
AI Assistant
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Button, Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { X } from '@signozhq/icons';
|
||||
|
||||
@@ -158,23 +158,23 @@
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
> button {
|
||||
.tab {
|
||||
border: 1px solid var(--l1-border);
|
||||
width: 114px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
.tab::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
|
||||
&[data-state='on'] {
|
||||
background: var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
border: 1px solid var(--l1-border);
|
||||
.selected_view {
|
||||
background: var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
border: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
}
|
||||
.selected_view::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@ 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 } from '@signozhq/ui/button';
|
||||
import { Drawer, Tooltip } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { Divider, Drawer, Radio, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
import cx from 'classnames';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
|
||||
@@ -198,8 +197,8 @@ function LogDetailInner({
|
||||
|
||||
const LogJsonData = log ? aggregateAttributesResourcesToString(log) : '';
|
||||
|
||||
const handleModeChange = (value: string): void => {
|
||||
setSelectedView(value as VIEWS);
|
||||
const handleModeChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setIsEdit(false);
|
||||
setIsFilterVisible(false);
|
||||
};
|
||||
@@ -453,50 +452,56 @@ function LogDetailInner({
|
||||
</div>
|
||||
|
||||
<div className="tabs-and-search">
|
||||
<ToggleGroupSimple
|
||||
type="single"
|
||||
<Radio.Group
|
||||
className="views-tabs"
|
||||
onChange={handleModeChange}
|
||||
value={selectedView}
|
||||
items={[
|
||||
{
|
||||
value: VIEW_TYPES.OVERVIEW,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<Table size={14} />
|
||||
Overview
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: VIEW_TYPES.JSON,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<Braces size={14} />
|
||||
JSON
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: VIEW_TYPES.CONTEXT,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<TextSelect size={14} />
|
||||
Context
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: VIEW_TYPES.INFRAMETRICS,
|
||||
label: (
|
||||
<div className="view-title">
|
||||
<Histogram size="md" />
|
||||
Metrics
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
>
|
||||
<Radio.Button
|
||||
className={
|
||||
selectedView === VIEW_TYPES.OVERVIEW ? 'selected_view tab' : 'tab'
|
||||
}
|
||||
value={VIEW_TYPES.OVERVIEW}
|
||||
>
|
||||
<div className="view-title">
|
||||
<Table size={14} />
|
||||
Overview
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
className={
|
||||
selectedView === VIEW_TYPES.JSON ? 'selected_view tab' : 'tab'
|
||||
}
|
||||
value={VIEW_TYPES.JSON}
|
||||
>
|
||||
<div className="view-title">
|
||||
<Braces size={14} />
|
||||
JSON
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
className={
|
||||
selectedView === VIEW_TYPES.CONTEXT ? 'selected_view tab' : 'tab'
|
||||
}
|
||||
value={VIEW_TYPES.CONTEXT}
|
||||
>
|
||||
<div className="view-title">
|
||||
<TextSelect size={14} />
|
||||
Context
|
||||
</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
className={
|
||||
selectedView === VIEW_TYPES.INFRAMETRICS ? 'selected_view tab' : 'tab'
|
||||
}
|
||||
value={VIEW_TYPES.INFRAMETRICS}
|
||||
>
|
||||
<div className="view-title">
|
||||
<Histogram size="md" />
|
||||
Metrics
|
||||
</div>
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
|
||||
<div className="log-detail-drawer__actions">
|
||||
{selectedView === VIEW_TYPES.CONTEXT && (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -18,8 +18,7 @@ import {
|
||||
RefreshCw,
|
||||
} from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Select } from 'antd';
|
||||
import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import { Button, Checkbox, Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import TextToolTip from 'components/TextToolTip/TextToolTip';
|
||||
@@ -750,7 +749,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
>
|
||||
<Checkbox
|
||||
value={isSelected}
|
||||
checked={isSelected}
|
||||
className="option-checkbox"
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
@@ -1585,7 +1584,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Checkbox value={allOptionsSelected} className="option-checkbox">
|
||||
<Checkbox checked={allOptionsSelected} className="option-checkbox">
|
||||
<div className="option-content">
|
||||
<div className="all-option-text">ALL</div>
|
||||
</div>
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
@keyframes noz-wave-wiggle {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(-20deg);
|
||||
}
|
||||
}
|
||||
|
||||
.noz {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
overflow: visible;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:global {
|
||||
.noz-arm-left {
|
||||
transform-origin: 4.18383px 13.4752px;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.55s cubic-bezier(0.34, 1.7, 0.5, 1);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.noz-arm-wiggle {
|
||||
transform-origin: 4.18383px 13.4752px;
|
||||
transform: rotate(0deg);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.noz-head {
|
||||
transform-origin: 12.02px 18.37px;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.5s cubic-bezier(0.34, 1.7, 0.5, 1);
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
:global(.noz-arm-left) {
|
||||
transform: rotate(145deg) scale(1, 1.7);
|
||||
}
|
||||
|
||||
:global(.noz-arm-wiggle) {
|
||||
animation: noz-wave-wiggle 0.7s ease-in-out 0.2s infinite;
|
||||
}
|
||||
|
||||
:global(.noz-head) {
|
||||
transform: rotate(9deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.noz-wave):hover {
|
||||
:global(.noz-arm-left) {
|
||||
transform: rotate(145deg) scale(1, 1.7);
|
||||
}
|
||||
|
||||
:global(.noz-arm-wiggle) {
|
||||
animation: noz-wave-wiggle 0.7s ease-in-out 0.2s infinite;
|
||||
}
|
||||
|
||||
:global(.noz-head) {
|
||||
transform: rotate(9deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.noz {
|
||||
:global(.noz-arm-left),
|
||||
:global(.noz-arm-wiggle),
|
||||
:global(.noz-head) {
|
||||
transition: none;
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import cx from 'classnames';
|
||||
|
||||
import styles from './Noz.module.scss';
|
||||
|
||||
interface NozProps {
|
||||
size?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Noz — the SigNoz AI assistant mascot. Waves on hover.
|
||||
*
|
||||
* Hover behavior:
|
||||
* - Hovering the icon itself triggers the wave.
|
||||
* - To make a parent element (e.g. a Button) trigger the wave on its own
|
||||
* hover, add the class `noz-wave` to that parent.
|
||||
*/
|
||||
export default function Noz({ size = 24, className }: NozProps): JSX.Element {
|
||||
return (
|
||||
<span
|
||||
className={cx(styles.noz, className)}
|
||||
style={{ width: size, height: size }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
viewBox="-2 0.5 28 28"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{/* body */}
|
||||
<rect
|
||||
x="4.35938"
|
||||
y="8.49908"
|
||||
width="15.4569"
|
||||
height="11.978"
|
||||
rx="1.76147"
|
||||
fill="#E5484D"
|
||||
/>
|
||||
{/* legs */}
|
||||
<rect
|
||||
x="6.87012"
|
||||
y="19.0679"
|
||||
width="3.34679"
|
||||
height="3.69908"
|
||||
rx="0.880734"
|
||||
fill="#E5484D"
|
||||
/>
|
||||
<rect
|
||||
x="13.916"
|
||||
y="19.0679"
|
||||
width="3.34679"
|
||||
height="3.69908"
|
||||
rx="0.880734"
|
||||
fill="#E5484D"
|
||||
/>
|
||||
{/* right arm (static) */}
|
||||
<rect
|
||||
x="18.7598"
|
||||
y="13.4752"
|
||||
width="2.11376"
|
||||
height="3.69908"
|
||||
rx="0.880734"
|
||||
fill="#E5484D"
|
||||
/>
|
||||
{/* left arm: outer group does the "lift", inner does the wiggle */}
|
||||
<g className="noz-arm-left">
|
||||
<g className="noz-arm-wiggle">
|
||||
<rect
|
||||
x="3.12695"
|
||||
y="13.4752"
|
||||
width="2.11376"
|
||||
height="3.69908"
|
||||
rx="0.880734"
|
||||
fill="#E5484D"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
{/* head: face + eye + hat tilt together */}
|
||||
<g className="noz-head">
|
||||
<circle cx="12.0217" cy="14.4881" r="3.87523" fill="#F5F5F5" />
|
||||
<path
|
||||
d="M12.0237 12.8024C12.0237 13.7328 11.2673 14.4892 10.337 14.4892C10.0339 14.4892 9.74926 14.4101 9.50152 14.2678C9.47517 14.5551 9.49888 14.8502 9.57795 15.1428C9.93901 16.4921 11.3279 17.2933 12.6773 16.9323C14.0267 16.5712 14.8279 15.1823 14.4668 13.8329C14.1453 12.6285 13.0041 11.8616 11.8023 11.967C11.942 12.2121 12.0237 12.4967 12.0237 12.8024Z"
|
||||
fill="#0A0C10"
|
||||
/>
|
||||
<path
|
||||
d="M8.33833 7.94578L9.83358 4.31319C10.1302 3.59261 10.6676 2.99939 11.355 2.63299L13.9181 1.26684C14.1327 1.15169 14.3804 1.34885 14.3194 1.58439L13.6703 4.06892C13.6511 4.14046 13.6424 4.21374 13.6424 4.28876C13.6424 4.39868 13.6633 4.5086 13.7052 4.61154L15.0382 7.94578H11.4248L11.6307 7.32813L12.3356 7.09259C12.449 7.05421 12.5257 6.94778 12.5257 6.82739C12.5257 6.707 12.449 6.60057 12.3356 6.56218L11.6307 6.32664L11.3951 5.62176C11.3568 5.51009 11.2503 5.43333 11.1299 5.43333C11.0096 5.43333 10.9031 5.5101 10.8647 5.6235L10.6292 6.32839L9.92431 6.56393C9.8109 6.60231 9.73413 6.70874 9.73413 6.82913C9.73413 6.94952 9.8109 7.05595 9.92431 7.09434L10.6292 7.32988L10.8351 7.94752H8.33833V7.94578ZM12.1 3.43558C12.0808 3.378 12.0285 3.33962 11.9674 3.33962C11.9064 3.33962 11.854 3.378 11.8348 3.43558L11.7179 3.78802L11.3655 3.90492C11.3079 3.92411 11.2695 3.97645 11.2695 4.03752C11.2695 4.09859 11.3079 4.15093 11.3655 4.17012L11.7179 4.28702L11.8348 4.63946C11.854 4.69704 11.9064 4.73542 11.9674 4.73542C12.0285 4.73542 12.0808 4.69704 12.1 4.63946L12.2169 4.28702L12.5694 4.17012C12.6269 4.15093 12.6653 4.09859 12.6653 4.03752C12.6653 3.97645 12.6269 3.92411 12.5694 3.90492L12.2169 3.78802L12.1 3.43558ZM7.78 7.91088H15.5965C15.9053 7.91088 16.1548 8.16038 16.1548 8.4692C16.1548 8.77803 15.9053 9.02753 15.5965 9.02753H7.78C7.47118 9.02753 7.22168 8.77803 7.22168 8.4692C7.22168 8.16038 7.47118 7.91088 7.78 7.91088Z"
|
||||
fill="#4E74F8"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Noz.defaultProps = {
|
||||
size: 24,
|
||||
className: undefined,
|
||||
};
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
.query-add-ons {
|
||||
width: 100%;
|
||||
|
||||
.add-on-tab-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--margin-2);
|
||||
}
|
||||
}
|
||||
|
||||
.add-ons-list {
|
||||
@@ -32,7 +25,7 @@
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
> button {
|
||||
.tab {
|
||||
border: 1px solid var(--l1-border);
|
||||
border-left: none;
|
||||
min-width: 120px;
|
||||
@@ -42,21 +35,21 @@
|
||||
&:first-child {
|
||||
border-left: 1px solid var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
.tab::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
|
||||
&[data-state='on'] {
|
||||
color: var(--text-robin-500);
|
||||
border: 1px solid var(--l1-border);
|
||||
.selected-view {
|
||||
color: var(--text-robin-500);
|
||||
border: 1px solid var(--l1-border);
|
||||
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
}
|
||||
.selected-view::before {
|
||||
background: var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,14 +259,6 @@
|
||||
border-left: transparent;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
|
||||
&:focus:not(:focus-visible),
|
||||
&.ant-btn:focus:not(:focus-visible) {
|
||||
border-color: var(--l2-border);
|
||||
border-left-color: transparent;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,21 +284,5 @@
|
||||
.cm-placeholder {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
$add-on-row-height: 38px;
|
||||
|
||||
.periscope-input-with-label {
|
||||
.input {
|
||||
.ant-select {
|
||||
height: $add-on-row-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-with-label {
|
||||
.input {
|
||||
height: $add-on-row-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { Button, Radio, RadioChangeEvent, Tooltip } from 'antd';
|
||||
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GroupByFilter } from 'container/QueryBuilder/filters/GroupByFilter/GroupByFilter';
|
||||
@@ -251,7 +250,8 @@ function QueryAddOns({
|
||||
);
|
||||
}, [panelType, isListViewPanel, query, showReduceTo]);
|
||||
|
||||
const handleOptionClick = (clickedAddOn: AddOn): void => {
|
||||
const handleOptionClick = (e: RadioChangeEvent): void => {
|
||||
const clickedAddOn = e.target.value as AddOn;
|
||||
const isAlreadySelected = selectedViews.some(
|
||||
(view) => view.key === clickedAddOn.key,
|
||||
);
|
||||
@@ -515,27 +515,15 @@ function QueryAddOns({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ToggleGroupSimple
|
||||
type="multiple"
|
||||
className="add-ons-tabs"
|
||||
value={selectedViews.map((view) => view.key)}
|
||||
onChange={(newKeys: string[]): void => {
|
||||
const oldKeys = selectedViews.map((view) => view.key);
|
||||
const toggledKey =
|
||||
newKeys.find((k) => !oldKeys.includes(k)) ??
|
||||
oldKeys.find((k) => !newKeys.includes(k));
|
||||
if (!toggledKey) {
|
||||
return;
|
||||
}
|
||||
const clickedAddOn = addOns.find((a) => a.key === toggledKey);
|
||||
if (clickedAddOn) {
|
||||
handleOptionClick(clickedAddOn);
|
||||
}
|
||||
}}
|
||||
items={addOns.map((addOn) => ({
|
||||
value: addOn.key,
|
||||
label: (
|
||||
<div className="add-ons-list">
|
||||
<Radio.Group
|
||||
className="add-ons-tabs"
|
||||
onChange={handleOptionClick}
|
||||
value={selectedViews}
|
||||
>
|
||||
{addOns.map((addOn) => (
|
||||
<Tooltip
|
||||
key={addOn.key}
|
||||
title={
|
||||
<TooltipContent
|
||||
label={addOn.label}
|
||||
@@ -546,17 +534,26 @@ function QueryAddOns({
|
||||
placement="top"
|
||||
mouseEnterDelay={0.5}
|
||||
>
|
||||
<span
|
||||
className="add-on-tab-title"
|
||||
data-testid={`query-add-on-${addOn.key}`}
|
||||
<Radio.Button
|
||||
className={
|
||||
selectedViews.find((view) => view.key === addOn.key)
|
||||
? 'selected-view tab'
|
||||
: 'tab'
|
||||
}
|
||||
value={addOn}
|
||||
>
|
||||
{addOn.icon}
|
||||
{addOn.label}
|
||||
</span>
|
||||
<div
|
||||
className="add-on-tab-title"
|
||||
data-testid={`query-add-on-${addOn.key}`}
|
||||
>
|
||||
{addOn.icon}
|
||||
{addOn.label}
|
||||
</div>
|
||||
</Radio.Button>
|
||||
</Tooltip>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { copilot } from '@uiw/codemirror-theme-copilot';
|
||||
import { githubLight } from '@uiw/codemirror-theme-github';
|
||||
import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror';
|
||||
import { Button, Card, Collapse, Popover, Tooltip } from 'antd';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Button, Card, Collapse, Popover, Tag, Tooltip } from 'antd';
|
||||
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
|
||||
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
|
||||
import cx from 'classnames';
|
||||
@@ -665,26 +664,26 @@ function QuerySearch({
|
||||
// Helper function to render a badge for the current context mode
|
||||
const renderContextBadge = (): JSX.Element => {
|
||||
if (!editingMode) {
|
||||
return <Badge color="vanilla">Unknown</Badge>;
|
||||
return <Tag>Unknown</Tag>;
|
||||
}
|
||||
|
||||
switch (editingMode) {
|
||||
case 'key':
|
||||
return <Badge color="robin">Key</Badge>;
|
||||
return <Tag color="blue">Key</Tag>;
|
||||
case 'operator':
|
||||
return <Badge color="sakura">Operator</Badge>;
|
||||
return <Tag color="purple">Operator</Tag>;
|
||||
case 'value':
|
||||
return <Badge color="forest">Value</Badge>;
|
||||
return <Tag color="green">Value</Tag>;
|
||||
case 'conjunction':
|
||||
return <Badge color="amber">Conjunction</Badge>;
|
||||
return <Tag color="orange">Conjunction</Tag>;
|
||||
case 'function':
|
||||
return <Badge color="aqua">Function</Badge>;
|
||||
return <Tag color="cyan">Function</Tag>;
|
||||
case 'parenthesis':
|
||||
return <Badge color="sakura">Parenthesis</Badge>;
|
||||
return <Tag color="magenta">Parenthesis</Tag>;
|
||||
case 'bracketList':
|
||||
return <Badge color="cherry">Bracket List</Badge>;
|
||||
return <Tag color="red">Bracket List</Tag>;
|
||||
default:
|
||||
return <Badge color="vanilla">Unknown</Badge>;
|
||||
return <Tag>Unknown</Tag>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1305,37 +1304,34 @@ function QuerySearch({
|
||||
Currently editing: {renderContextBadge()}
|
||||
{queryContext?.keyToken && (
|
||||
<span className="triplet-info">
|
||||
Key: <Badge color="vanilla">{queryContext.keyToken}</Badge>
|
||||
Key: <Tag>{queryContext.keyToken}</Tag>
|
||||
</span>
|
||||
)}
|
||||
{queryContext?.operatorToken && (
|
||||
<span className="triplet-info">
|
||||
Operator: <Badge color="vanilla">{queryContext.operatorToken}</Badge>
|
||||
Operator: <Tag>{queryContext.operatorToken}</Tag>
|
||||
</span>
|
||||
)}
|
||||
{queryContext?.valueToken && (
|
||||
<span className="triplet-info">
|
||||
Value: <Badge color="vanilla">{queryContext.valueToken}</Badge>
|
||||
Value: <Tag>{queryContext.valueToken}</Tag>
|
||||
</span>
|
||||
)}
|
||||
{queryContext?.currentPair && (
|
||||
<span className="triplet-info query-pair-info">
|
||||
Current pair: <Badge color="robin">{queryContext.currentPair.key}</Badge>
|
||||
<Badge color="sakura">{queryContext.currentPair.operator}</Badge>
|
||||
Current pair: <Tag color="blue">{queryContext.currentPair.key}</Tag>
|
||||
<Tag color="purple">{queryContext.currentPair.operator}</Tag>
|
||||
{queryContext.currentPair.value && (
|
||||
<Badge color="forest">{queryContext.currentPair.value}</Badge>
|
||||
<Tag color="green">{queryContext.currentPair.value}</Tag>
|
||||
)}
|
||||
<Badge
|
||||
color={queryContext.currentPair.isComplete ? 'success' : 'warning'}
|
||||
>
|
||||
<Tag color={queryContext.currentPair.isComplete ? 'success' : 'warning'}>
|
||||
{queryContext.currentPair.isComplete ? 'Complete' : 'Incomplete'}
|
||||
</Badge>
|
||||
</Tag>
|
||||
</span>
|
||||
)}
|
||||
{queryContext?.queryPairs && queryContext.queryPairs.length > 0 && (
|
||||
<span className="triplet-info">
|
||||
Total pairs:{' '}
|
||||
<Badge color="robin">{queryContext.queryPairs.length}</Badge>
|
||||
Total pairs: <Tag color="blue">{queryContext.queryPairs.length}</Tag>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
|
||||
import { Dropdown } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { ENTITY_VERSION_V4, ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import QBEntityOptions from 'container/QueryBuilder/components/QBEntityOptions/QBEntityOptions';
|
||||
import { QueryProps } from 'container/QueryBuilder/type';
|
||||
import { QueryProps } from 'container/QueryBuilder/components/Query/Query.interfaces';
|
||||
import SpanScopeSelector from 'container/QueryBuilder/filters/QueryBuilderSearchV2/SpanScopeSelector';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
@@ -195,7 +195,7 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
)}
|
||||
|
||||
{isMultiQueryAllowed && (
|
||||
<DropdownMenuSimple
|
||||
<Dropdown
|
||||
className="query-actions-dropdown"
|
||||
menu={{
|
||||
items: [
|
||||
@@ -217,10 +217,10 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
: []),
|
||||
],
|
||||
}}
|
||||
align="end"
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Ellipsis size={16} />
|
||||
</DropdownMenuSimple>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,23 +4,6 @@
|
||||
padding: 12px;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
|
||||
.search {
|
||||
input {
|
||||
--input-background: var(--l2-background);
|
||||
--input-hover-background: var(--l2-background);
|
||||
--input-focus-background: var(--l2-background);
|
||||
&::placeholder {
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
--input-font-size: 14px;
|
||||
--input-border-color: var(--l1-border);
|
||||
--input-focus-border-color: var(--primary-background);
|
||||
--input-focus-outline-width: 0;
|
||||
--input-focus-outline-offset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-header-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -72,7 +55,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 24px;
|
||||
|
||||
.checkbox-value-section {
|
||||
display: flex;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* eslint-disable sonarjs/no-identical-functions */
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Skeleton } from 'antd';
|
||||
import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import { Button, Checkbox, Input, Skeleton } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
|
||||
@@ -636,12 +634,10 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
)}
|
||||
<div className="value">
|
||||
<Checkbox
|
||||
onChange={(checked): void =>
|
||||
onChange(value, checked === true, false)
|
||||
}
|
||||
value={currentFilterState[value]}
|
||||
onChange={(e): void => onChange(value, e.target.checked, false)}
|
||||
checked={currentFilterState[value]}
|
||||
disabled={isFilterDisabled}
|
||||
className="check-box"
|
||||
rootClassName="check-box"
|
||||
/>
|
||||
|
||||
<div
|
||||
|
||||
@@ -51,10 +51,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.duration-input-slider {
|
||||
padding: 12px 0px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Button, Input } from 'antd';
|
||||
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ import type {
|
||||
TableColumnsType as ColumnsType,
|
||||
TableColumnType as ColumnType,
|
||||
} from 'antd';
|
||||
import { Button, Flex } from 'antd';
|
||||
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
|
||||
import { Button, Dropdown, Flex, MenuProps } from 'antd';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { SlidersHorizontal } from '@signozhq/icons';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import ResizeTable from './ResizeTable';
|
||||
import { DynamicColumnTableProps } from './types';
|
||||
@@ -84,9 +84,8 @@ function DynamicColumnTable({
|
||||
);
|
||||
};
|
||||
|
||||
const items: MenuItem[] =
|
||||
const items: MenuProps['items'] =
|
||||
dynamicColumns?.map((column, index) => ({
|
||||
key: String(index),
|
||||
label: (
|
||||
<div
|
||||
className="dynamicColumnsTable-items"
|
||||
@@ -100,6 +99,8 @@ function DynamicColumnTable({
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
key: index,
|
||||
type: 'checkbox',
|
||||
})) || [];
|
||||
|
||||
// Get current page from URL or default to 1
|
||||
@@ -128,14 +129,18 @@ function DynamicColumnTable({
|
||||
<Flex justify="flex-end" align="center" gap={8}>
|
||||
{facingIssueBtn && <LaunchChatSupport {...facingIssueBtn} />}
|
||||
{dynamicColumns && (
|
||||
<DropdownMenuSimple menu={{ items }}>
|
||||
<Dropdown
|
||||
getPopupContainer={popupContainer}
|
||||
menu={{ items }}
|
||||
trigger={['click']}
|
||||
>
|
||||
<Button
|
||||
className="dynamicColumnTable-button filter-btn"
|
||||
size="middle"
|
||||
icon={<SlidersHorizontal size={14} />}
|
||||
data-testid="additional-filters-button"
|
||||
/>
|
||||
</DropdownMenuSimple>
|
||||
</Dropdown>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CircleAlert, RefreshCw } from '@signozhq/icons';
|
||||
import { Select } from 'antd';
|
||||
import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import { Checkbox, Select } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { useListRoles } from 'api/generated/services/role';
|
||||
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
@@ -147,11 +146,12 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
|
||||
options={options}
|
||||
optionFilterProp="label"
|
||||
optionRender={(option): JSX.Element => (
|
||||
<div style={{ pointerEvents: 'none' }}>
|
||||
<Checkbox value={value.includes(option.value as string)}>
|
||||
{option.label}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<Checkbox
|
||||
checked={value.includes(option.value as string)}
|
||||
style={{ pointerEvents: 'none' }}
|
||||
>
|
||||
{option.label}
|
||||
</Checkbox>
|
||||
)}
|
||||
getPopupContainer={getPopupContainer}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
.route-tab-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.route-tab-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user