mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-04 16:10:26 +01:00
Compare commits
1 Commits
feat/chart
...
test/flaky
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1cff50fc6 |
26
.github/CODEOWNERS
vendored
26
.github/CODEOWNERS
vendored
@@ -118,9 +118,6 @@ go.mod @therealpandey
|
||||
|
||||
/tests/integration/ @therealpandey
|
||||
|
||||
# e2e tests
|
||||
/tests/e2e/ @AshwinBhatkal
|
||||
|
||||
# Flagger Owners
|
||||
|
||||
/pkg/flagger/ @therealpandey
|
||||
@@ -165,26 +162,3 @@ go.mod @therealpandey
|
||||
/frontend/src/lib/dashboard/ @SigNoz/pulse-frontend
|
||||
/frontend/src/lib/dashboardVariables/ @SigNoz/pulse-frontend
|
||||
/frontend/src/components/NewSelect/ @SigNoz/pulse-frontend
|
||||
|
||||
## Dashboard V2
|
||||
/frontend/src/pages/DashboardPageV2/ @SigNoz/pulse-frontend
|
||||
/frontend/src/pages/DashboardsListPageV2/ @SigNoz/pulse-frontend
|
||||
|
||||
## Infrastructure Monitoring
|
||||
/frontend/src/pages/InfrastructureMonitoring/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/InfraMonitoringHosts/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/InfraMonitoringK8s/ @SigNoz/pulse-frontend
|
||||
|
||||
## Alerts
|
||||
/frontend/src/pages/AlertList/ @SigNoz/pulse-frontend
|
||||
/frontend/src/pages/AlertDetails/ @SigNoz/pulse-frontend
|
||||
/frontend/src/pages/CreateAlert/ @SigNoz/pulse-frontend
|
||||
/frontend/src/pages/EditRules/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/AlertHistory/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/CreateAlertRule/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/CreateAlertV2/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/EditAlertV2/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/FormAlertRules/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/ListAlertRules/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/TriggeredAlerts/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/AnomalyAlertEvaluationView/ @SigNoz/pulse-frontend
|
||||
|
||||
1
.github/workflows/build-community.yaml
vendored
1
.github/workflows/build-community.yaml
vendored
@@ -54,7 +54,6 @@ jobs:
|
||||
JS_SRC: frontend
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
JS_PKG_MANAGER: pnpm
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
|
||||
3
.github/workflows/build-enterprise.yaml
vendored
3
.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
|
||||
@@ -85,7 +87,6 @@ jobs:
|
||||
JS_INPUT_ARTIFACT_PATH: frontend/.env
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: enterprise-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
JS_PKG_MANAGER: pnpm
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
|
||||
1
.github/workflows/build-staging.yaml
vendored
1
.github/workflows/build-staging.yaml
vendored
@@ -86,7 +86,6 @@ jobs:
|
||||
JS_INPUT_ARTIFACT_PATH: frontend/.env
|
||||
JS_OUTPUT_ARTIFACT_CACHE_KEY: staging-jsbuild-${{ github.sha }}
|
||||
JS_OUTPUT_ARTIFACT_PATH: frontend/build
|
||||
JS_PKG_MANAGER: pnpm
|
||||
DOCKER_BUILD: false
|
||||
DOCKER_MANIFEST: false
|
||||
go-build:
|
||||
|
||||
22
.github/workflows/e2eci.yaml
vendored
22
.github/workflows/e2eci.yaml
vendored
@@ -21,19 +21,15 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: install
|
||||
run: |
|
||||
cd tests/e2e && pnpm install
|
||||
cd tests/e2e && yarn install --frozen-lockfile
|
||||
- name: fmt
|
||||
run: |
|
||||
cd tests/e2e && pnpm fmt:check
|
||||
cd tests/e2e && yarn fmt:check
|
||||
- name: lint
|
||||
run: |
|
||||
cd tests/e2e && pnpm lint
|
||||
cd tests/e2e && yarn lint
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -58,19 +54,15 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: python-install
|
||||
run: |
|
||||
cd tests && uv sync
|
||||
- name: pnpm-install
|
||||
- name: yarn-install
|
||||
run: |
|
||||
cd tests/e2e && pnpm install --frozen-lockfile
|
||||
cd tests/e2e && yarn install --frozen-lockfile
|
||||
- name: playwright-browsers
|
||||
run: |
|
||||
cd tests/e2e && pnpm playwright install --with-deps ${{ matrix.project }}
|
||||
cd tests/e2e && yarn playwright install --with-deps ${{ matrix.project }}
|
||||
- name: bring-up-stack
|
||||
run: |
|
||||
cd tests && \
|
||||
@@ -81,7 +73,7 @@ jobs:
|
||||
- name: playwright-test
|
||||
run: |
|
||||
cd tests/e2e && \
|
||||
pnpm playwright test --project=${{ matrix.project }}
|
||||
yarn playwright test --project=${{ matrix.project }}
|
||||
- name: teardown-stack
|
||||
if: always()
|
||||
run: |
|
||||
|
||||
21
.github/workflows/goci.yaml
vendored
21
.github/workflows/goci.yaml
vendored
@@ -77,10 +77,6 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: setup-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: docker-community
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -123,20 +119,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)
|
||||
|
||||
4
.github/workflows/gor-signoz-community.yaml
vendored
4
.github/workflows/gor-signoz-community.yaml
vendored
@@ -25,10 +25,6 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: setup-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: build-frontend
|
||||
run: make js-build
|
||||
- name: upload-frontend-artifact
|
||||
|
||||
6
.github/workflows/gor-signoz.yaml
vendored
6
.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
|
||||
@@ -39,10 +41,6 @@ jobs:
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: setup-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: build-frontend
|
||||
run: make js-build
|
||||
- name: upload-frontend-artifact
|
||||
|
||||
1
.github/workflows/integrationci.yaml
vendored
1
.github/workflows/integrationci.yaml
vendored
@@ -52,7 +52,6 @@ jobs:
|
||||
- rootuser
|
||||
- serviceaccount
|
||||
- querier_json_body
|
||||
- querier_skip_resource_fingerprint
|
||||
- ttl
|
||||
sqlstore-provider:
|
||||
- postgres
|
||||
|
||||
37
.github/workflows/jsci.yaml
vendored
37
.github/workflows/jsci.yaml
vendored
@@ -22,7 +22,6 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
test:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -33,7 +32,6 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
fmt:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -44,7 +42,6 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
lint:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -55,7 +52,6 @@ jobs:
|
||||
with:
|
||||
PRIMUS_REF: main
|
||||
JS_SRC: frontend
|
||||
JS_PKG_MANAGER: pnpm
|
||||
languages:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
@@ -80,36 +76,9 @@ jobs:
|
||||
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
|
||||
run: cd frontend && yarn install
|
||||
- name: generate-api-clients
|
||||
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)
|
||||
cd frontend && yarn generate:api
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated api clients. Run yarn generate:api in frontend/ locally and commit."; exit 1)
|
||||
|
||||
10
.gitpod.yml
10
.gitpod.yml
@@ -1,21 +1,19 @@
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
|
||||
|
||||
tasks:
|
||||
- name: Run Docker Images
|
||||
init: |
|
||||
cd ./deploy/docker
|
||||
sudo docker compose up -d
|
||||
|
||||
- name: Install pnpm
|
||||
init: |
|
||||
npm i -g pnpm
|
||||
|
||||
- name: Run Frontend
|
||||
init: |
|
||||
cd ./frontend
|
||||
pnpm install
|
||||
command: pnpm dev
|
||||
yarn install
|
||||
command:
|
||||
yarn dev
|
||||
|
||||
ports:
|
||||
- port: 8080
|
||||
|
||||
@@ -8,14 +8,6 @@ packages:
|
||||
filename: "alertmanager.go"
|
||||
structname: 'Mock{{.InterfaceName}}'
|
||||
pkgname: '{{.SrcPackageName}}test'
|
||||
github.com/SigNoz/signoz/pkg/types/alertmanagertypes:
|
||||
interfaces:
|
||||
MaintenanceStore:
|
||||
config:
|
||||
dir: '{{.InterfaceDir}}/alertmanagertypestest'
|
||||
filename: "maintenance.go"
|
||||
structname: 'Mock{{.InterfaceName}}'
|
||||
pkgname: '{{.SrcPackageName}}test'
|
||||
github.com/SigNoz/signoz/pkg/tokenizer:
|
||||
config:
|
||||
all: true
|
||||
|
||||
2
Makefile
2
Makefile
@@ -154,7 +154,7 @@ $(GO_BUILD_ARCHS_ENTERPRISE_RACE): go-build-enterprise-race-%: $(TARGET_DIR)
|
||||
.PHONY: js-build
|
||||
js-build: ## Builds the js frontend
|
||||
@echo ">> building js frontend"
|
||||
@cd $(JS_BUILD_CONTEXT) && CI=1 pnpm install && pnpm build
|
||||
@cd $(JS_BUILD_CONTEXT) && CI=1 yarn install && yarn build
|
||||
|
||||
##############################################################
|
||||
# docker commands
|
||||
|
||||
55
cmd/authz.go
55
cmd/authz.go
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
@@ -24,7 +23,6 @@ export default {
|
||||
{
|
||||
kind: '{{ .Kind }}',
|
||||
type: '{{ .Type }}',
|
||||
{{ .FormattedAllowedVerbs }}
|
||||
},
|
||||
{{- end }}
|
||||
],
|
||||
@@ -43,9 +41,8 @@ type permissionsTypeRelation struct {
|
||||
}
|
||||
|
||||
type permissionsTypeResource struct {
|
||||
Kind string
|
||||
Type string
|
||||
FormattedAllowedVerbs string
|
||||
Kind string
|
||||
Type string
|
||||
}
|
||||
|
||||
type permissionsTypeData struct {
|
||||
@@ -53,30 +50,6 @@ type permissionsTypeData struct {
|
||||
Relations []permissionsTypeRelation
|
||||
}
|
||||
|
||||
// formatAllowedVerbs returns a prettier-compatible formatted allowedVerbs line.
|
||||
// indentLevel is the number of tabs for the property (matching kind/type indent).
|
||||
// printWidth is prettier's printWidth; tabWidth is assumed to be 1 (each \t = 1 char).
|
||||
func formatAllowedVerbs(verbs []string, indentLevel int, printWidth int) string {
|
||||
quoted := make([]string, len(verbs))
|
||||
for i, v := range verbs {
|
||||
quoted[i] = "'" + v + "'"
|
||||
}
|
||||
indent := strings.Repeat("\t", indentLevel)
|
||||
|
||||
oneLine := indent + "allowedVerbs: [" + strings.Join(quoted, ", ") + "],"
|
||||
if len(oneLine) <= printWidth {
|
||||
return oneLine
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(indent + "allowedVerbs: [\n")
|
||||
for _, q := range quoted {
|
||||
b.WriteString(indent + "\t" + q + ",\n")
|
||||
}
|
||||
b.WriteString(indent + "],")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func registerGenerateAuthz(parentCmd *cobra.Command) {
|
||||
authzCmd := &cobra.Command{
|
||||
Use: "authz",
|
||||
@@ -93,9 +66,9 @@ func runGenerateAuthz(_ context.Context) error {
|
||||
registry := coretypes.NewRegistry()
|
||||
|
||||
allowedResources := map[string]bool{
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourcesRole).String(): true,
|
||||
}
|
||||
|
||||
allowedTypes := map[string]bool{}
|
||||
@@ -107,23 +80,9 @@ func runGenerateAuthz(_ context.Context) error {
|
||||
continue
|
||||
}
|
||||
allowedTypes[ref.Type.StringValue()] = true
|
||||
|
||||
resource, err := coretypes.NewResourceFromTypeAndKind(ref.Type, ref.Kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verbs := resource.AllowedVerbs()
|
||||
allowedVerbStrings := make([]string, 0, len(verbs))
|
||||
for _, verb := range verbs {
|
||||
allowedVerbStrings = append(allowedVerbStrings, verb.StringValue())
|
||||
}
|
||||
sort.Strings(allowedVerbStrings)
|
||||
|
||||
resources = append(resources, permissionsTypeResource{
|
||||
Kind: ref.Kind.String(),
|
||||
Type: ref.Type.StringValue(),
|
||||
FormattedAllowedVerbs: formatAllowedVerbs(allowedVerbStrings, 4, 80),
|
||||
Kind: ref.Kind.String(),
|
||||
Type: ref.Type.StringValue(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,6 @@ func main() {
|
||||
// register a list of commands to the root command
|
||||
registerServer(cmd.RootCmd, logger)
|
||||
cmd.RegisterGenerate(cmd.RootCmd, logger)
|
||||
cmd.RegisterMetastore(cmd.RootCmd, logger, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
|
||||
cmd.Execute(logger)
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
func sqlstoreProviderFactories() factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]] {
|
||||
return signoz.NewSQLStoreProviderFactories()
|
||||
}
|
||||
|
||||
func sqlschemaProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
|
||||
return signoz.NewSQLSchemaProviderFactories(sqlstore)
|
||||
}
|
||||
@@ -18,22 +18,18 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/gateway"
|
||||
"github.com/SigNoz/signoz/pkg/gateway/noopgateway"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
|
||||
"github.com/SigNoz/signoz/pkg/meterreporter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"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"
|
||||
@@ -41,6 +37,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
@@ -87,8 +84,10 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
signoz.NewEmailingProviderFactories(),
|
||||
signoz.NewCacheProviderFactories(),
|
||||
signoz.NewWebProviderFactories(config.Global),
|
||||
sqlschemaProviderFactories,
|
||||
sqlstoreProviderFactories(),
|
||||
func(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
|
||||
return signoz.NewSQLSchemaProviderFactories(sqlstore)
|
||||
},
|
||||
signoz.NewSQLStoreProviderFactories(),
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
@@ -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()
|
||||
@@ -110,13 +109,10 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(_ licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]] {
|
||||
return signoz.NewAuditorProviderFactories()
|
||||
},
|
||||
func(_ context.Context, _ factory.ProviderSettings, _ flagger.Flagger, _ licensing.Licensing, _ telemetrystore.TelemetryStore, _ retention.Getter, _ organization.Getter, _ zeus.Zeus) (factory.NamedMap[factory.ProviderFactory[meterreporter.Reporter, meterreporter.Config]], string) {
|
||||
return signoz.NewMeterReporterProviderFactories(), "noop"
|
||||
},
|
||||
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
|
||||
return querier.NewHandler(ps, q, a)
|
||||
},
|
||||
func(_ sqlstore.SQLStore, _ dashboard.Module, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
func(_ sqlstore.SQLStore, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
return implcloudintegration.NewModule(), nil
|
||||
},
|
||||
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,9 +3,8 @@ FROM node:22-bookworm AS build
|
||||
WORKDIR /opt/
|
||||
COPY ./frontend/ ./
|
||||
ENV NODE_OPTIONS=--max-old-space-size=8192
|
||||
RUN CI=1 npm i -g pnpm@10
|
||||
RUN CI=1 pnpm install
|
||||
RUN CI=1 pnpm build
|
||||
RUN CI=1 yarn install
|
||||
RUN CI=1 yarn build
|
||||
|
||||
FROM golang:1.25-bookworm
|
||||
|
||||
@@ -35,7 +34,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
|
||||
|
||||
@@ -14,7 +14,6 @@ func main() {
|
||||
// register a list of commands to the root command
|
||||
registerServer(cmd.RootCmd, logger)
|
||||
cmd.RegisterGenerate(cmd.RootCmd, logger)
|
||||
cmd.RegisterMetastore(cmd.RootCmd, logger, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
|
||||
cmd.Execute(logger)
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
|
||||
)
|
||||
|
||||
func sqlstoreProviderFactories() factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]] {
|
||||
existingFactories := signoz.NewSQLStoreProviderFactories()
|
||||
if err := existingFactories.Add(postgressqlstore.NewFactory(sqlstorehook.NewLoggingFactory(), sqlstorehook.NewInstrumentationFactory())); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return existingFactories
|
||||
}
|
||||
|
||||
func sqlschemaProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
|
||||
existingFactories := signoz.NewSQLSchemaProviderFactories(sqlstore)
|
||||
if err := existingFactories.Add(postgressqlschema.NewFactory(sqlstore)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return existingFactories
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/metercollector"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrytraces"
|
||||
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/zeustypes"
|
||||
)
|
||||
|
||||
var meterConfigs = []metercollector.Config{
|
||||
{
|
||||
Provider: metercollector.ProviderStatic,
|
||||
Static: metercollector.StaticConfig{
|
||||
Name: zeustypes.MeterPlatformActive,
|
||||
Unit: zeustypes.MeterUnitCount,
|
||||
Aggregation: zeustypes.MeterAggregationMax,
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Provider: metercollector.ProviderTelemetry,
|
||||
Telemetry: metercollector.TelemetryConfig{
|
||||
Name: zeustypes.MeterLogSize,
|
||||
Unit: zeustypes.MeterUnitBytes,
|
||||
Aggregation: zeustypes.MeterAggregationSum,
|
||||
DBName: telemetrylogs.DBName,
|
||||
TableName: telemetrylogs.LogsV2LocalTableName,
|
||||
DefaultRetentionDays: retentiontypes.DefaultLogsRetentionDays,
|
||||
},
|
||||
},
|
||||
{
|
||||
Provider: metercollector.ProviderTelemetry,
|
||||
Telemetry: metercollector.TelemetryConfig{
|
||||
Name: zeustypes.MeterSpanSize,
|
||||
Unit: zeustypes.MeterUnitBytes,
|
||||
Aggregation: zeustypes.MeterAggregationSum,
|
||||
DBName: telemetrytraces.DBName,
|
||||
TableName: telemetrytraces.SpanIndexV3LocalTableName,
|
||||
DefaultRetentionDays: retentiontypes.DefaultTracesRetentionDays,
|
||||
},
|
||||
},
|
||||
{
|
||||
Provider: metercollector.ProviderTelemetry,
|
||||
Telemetry: metercollector.TelemetryConfig{
|
||||
Name: zeustypes.MeterDatapointCount,
|
||||
Unit: zeustypes.MeterUnitCount,
|
||||
Aggregation: zeustypes.MeterAggregationSum,
|
||||
DBName: telemetrymetrics.DBName,
|
||||
TableName: telemetrymetrics.SamplesV4LocalTableName,
|
||||
DefaultRetentionDays: retentiontypes.DefaultMetricsRetentionDays,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/SigNoz/signoz/cmd"
|
||||
"github.com/SigNoz/signoz/ee/auditor/fileauditor"
|
||||
"github.com/SigNoz/signoz/ee/auditor/otlphttpauditor"
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
|
||||
@@ -18,15 +17,14 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/gateway/httpgateway"
|
||||
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
"github.com/SigNoz/signoz/ee/metercollector/staticmetercollector"
|
||||
"github.com/SigNoz/signoz/ee/metercollector/telemetrymetercollector"
|
||||
"github.com/SigNoz/signoz/ee/meterreporter/httpmeterreporter"
|
||||
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration"
|
||||
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration/implcloudprovider"
|
||||
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
|
||||
eequerier "github.com/SigNoz/signoz/ee/querier"
|
||||
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
|
||||
eerules "github.com/SigNoz/signoz/ee/query-service/rules"
|
||||
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
enterprisezeus "github.com/SigNoz/signoz/ee/zeus"
|
||||
"github.com/SigNoz/signoz/ee/zeus/httpzeus"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
@@ -37,27 +35,25 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
pkgflagger "github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/gateway"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/meterreporter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
pkgcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
|
||||
"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/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"
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
@@ -91,6 +87,13 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
// print the version
|
||||
version.Info.PrettyPrint(config.Version)
|
||||
|
||||
// add enterprise sqlstore factories to the community sqlstore factories
|
||||
sqlstoreFactories := signoz.NewSQLStoreProviderFactories()
|
||||
if err := sqlstoreFactories.Add(postgressqlstore.NewFactory(sqlstorehook.NewLoggingFactory(), sqlstorehook.NewInstrumentationFactory())); err != nil {
|
||||
logger.ErrorContext(ctx, "failed to add postgressqlstore factory", errors.Attr(err))
|
||||
return err
|
||||
}
|
||||
|
||||
signoz, err := signoz.New(
|
||||
ctx,
|
||||
config,
|
||||
@@ -103,8 +106,15 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
signoz.NewEmailingProviderFactories(),
|
||||
signoz.NewCacheProviderFactories(),
|
||||
signoz.NewWebProviderFactories(config.Global),
|
||||
sqlschemaProviderFactories,
|
||||
sqlstoreProviderFactories(),
|
||||
func(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
|
||||
existingFactories := signoz.NewSQLSchemaProviderFactories(sqlstore)
|
||||
if err := existingFactories.Add(postgressqlschema.NewFactory(sqlstore)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return existingFactories
|
||||
},
|
||||
sqlstoreFactories,
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
samlCallbackAuthN, err := samlcallbackauthn.New(ctx, store, licensing)
|
||||
@@ -134,8 +144,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)
|
||||
@@ -145,30 +155,13 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
if err := factories.Add(otlphttpauditor.NewFactory(licensing, version.Info)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := factories.Add(fileauditor.NewFactory(licensing, version.Info)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return factories
|
||||
},
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, flagger pkgflagger.Flagger, licensing licensing.Licensing, telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter, orgGetter organization.Getter, zeus zeus.Zeus) (factory.NamedMap[factory.ProviderFactory[meterreporter.Reporter, meterreporter.Config]], string) {
|
||||
factories := signoz.NewMeterReporterProviderFactories()
|
||||
|
||||
collectorFactories := factory.MustNewNamedMap(
|
||||
staticmetercollector.NewFactory(),
|
||||
telemetrymetercollector.NewFactory(telemetryStore, retentionGetter),
|
||||
)
|
||||
|
||||
if err := factories.Add(httpmeterreporter.NewFactory(collectorFactories, meterConfigs, flagger, licensing, orgGetter, zeus)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return factories, "http"
|
||||
},
|
||||
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
|
||||
communityHandler := querier.NewHandler(ps, q, a)
|
||||
return eequerier.NewHandler(ps, q, communityHandler)
|
||||
},
|
||||
func(sqlStore sqlstore.SQLStore, dashboardModule dashboard.Module, global global.Global, zeus zeus.Zeus, gateway gateway.Gateway, licensing licensing.Licensing, serviceAccount serviceaccount.Module, config cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
func(sqlStore sqlstore.SQLStore, global global.Global, zeus zeus.Zeus, gateway gateway.Gateway, licensing licensing.Licensing, serviceAccount serviceaccount.Module, config cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
defStore := pkgcloudintegration.NewServiceDefinitionStore()
|
||||
awsCloudProviderModule, err := implcloudprovider.NewAWSCloudProvider(defStore)
|
||||
if err != nil {
|
||||
@@ -180,7 +173,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
|
||||
}
|
||||
|
||||
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), dashboardModule, global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
|
||||
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
|
||||
},
|
||||
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
|
||||
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, eerules.PrepareTaskFunc, eerules.TestNotification))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
154
cmd/metastore.go
154
cmd/metastore.go
@@ -1,154 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlmigration"
|
||||
"github.com/SigNoz/signoz/pkg/sqlmigrator"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
)
|
||||
|
||||
type SQLStoreProviderFactories func() factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]]
|
||||
type SQLSchemaProviderFactories func(sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]]
|
||||
|
||||
func RegisterMetastore(parentCmd *cobra.Command, logger *slog.Logger, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) {
|
||||
metastoreCmd := &cobra.Command{
|
||||
Use: "metastore",
|
||||
Short: "Run commands to interact with the Metastore",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
||||
}
|
||||
|
||||
registerMigrate(metastoreCmd, logger, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
|
||||
parentCmd.AddCommand(metastoreCmd)
|
||||
}
|
||||
|
||||
func registerMigrate(parentCmd *cobra.Command, logger *slog.Logger, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) {
|
||||
migrateCmd := &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run migrations for the Metastore",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
||||
}
|
||||
|
||||
registerSync(migrateCmd, logger, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
|
||||
parentCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
|
||||
func registerSync(parentCmd *cobra.Command, logger *slog.Logger, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) {
|
||||
syncCmd := &cobra.Command{
|
||||
Use: "sync",
|
||||
Short: "Runs 'sync' migrations for the metastore. Sync migrations are used to mutate schemas of the metastore. These migrations need to be successfully applied before bringing up the application.",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
||||
}
|
||||
|
||||
registerSyncUp(syncCmd, logger, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
registerSyncCheck(syncCmd, logger, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
|
||||
parentCmd.AddCommand(syncCmd)
|
||||
}
|
||||
|
||||
func registerSyncUp(parentCmd *cobra.Command, logger *slog.Logger, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) {
|
||||
var configFiles []string
|
||||
|
||||
syncUpCmd := &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "Runs 'up' migrations for the metastore. Up migrations are used to apply new migrations to the metastore",
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
|
||||
RunE: func(currCmd *cobra.Command, args []string) error {
|
||||
config, err := NewSigNozConfig(currCmd.Context(), logger, configFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runSyncUp(currCmd.Context(), config, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
},
|
||||
}
|
||||
|
||||
syncUpCmd.Flags().StringArrayVar(&configFiles, "config", nil, "path to a YAML configuration file (can be specified multiple times, later files override earlier ones)")
|
||||
parentCmd.AddCommand(syncUpCmd)
|
||||
}
|
||||
|
||||
func registerSyncCheck(parentCmd *cobra.Command, logger *slog.Logger, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) {
|
||||
var configFiles []string
|
||||
|
||||
syncCheckCmd := &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Runs a check for 'sync' migrations on the metastore. Returns a non-zero exit code if any migrations are pending.",
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
|
||||
RunE: func(currCmd *cobra.Command, args []string) error {
|
||||
config, err := NewSigNozConfig(currCmd.Context(), logger, configFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runSyncCheck(currCmd.Context(), config, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
},
|
||||
}
|
||||
|
||||
syncCheckCmd.Flags().StringArrayVar(&configFiles, "config", nil, "path to a YAML configuration file (can be specified multiple times, later files override earlier ones)")
|
||||
parentCmd.AddCommand(syncCheckCmd)
|
||||
}
|
||||
|
||||
func runSyncUp(ctx context.Context, config signoz.Config, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) error {
|
||||
migrator, err := newSyncMigrator(ctx, config, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migrator.Migrate(ctx)
|
||||
}
|
||||
|
||||
func runSyncCheck(ctx context.Context, config signoz.Config, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) error {
|
||||
migrator, err := newSyncMigrator(ctx, config, sqlstoreProviderFactories, sqlschemaProviderFactories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return migrator.Check(ctx)
|
||||
}
|
||||
|
||||
func newSyncMigrator(ctx context.Context, config signoz.Config, sqlstoreProviderFactories SQLStoreProviderFactories, sqlschemaProviderFactories SQLSchemaProviderFactories) (sqlmigrator.SQLMigrator, error) {
|
||||
instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providerSettings := instrumentation.ToProviderSettings()
|
||||
|
||||
sqlstore, err := factory.NewProviderFromNamedMap(ctx, providerSettings, config.SQLStore, sqlstoreProviderFactories(), config.SQLStore.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sqlschema, err := factory.NewProviderFromNamedMap(ctx, providerSettings, config.SQLSchema, sqlschemaProviderFactories(sqlstore), config.SQLStore.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
telemetrystore, err := factory.NewProviderFromNamedMap(ctx, providerSettings, config.TelemetryStore, signoz.NewTelemetryStoreProviderFactories(), config.TelemetryStore.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sqlmigrations, err := sqlmigration.New(ctx, providerSettings, config.SQLMigration, signoz.NewSQLMigrationProviderFactories(sqlstore, sqlschema, telemetrystore, providerSettings))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sqlmigrator.New(ctx, providerSettings, sqlstore, sqlmigrations, config.SQLMigrator), nil
|
||||
}
|
||||
@@ -60,20 +60,6 @@ web:
|
||||
index: index.html
|
||||
# The directory containing the static build files.
|
||||
directory: /etc/signoz/web
|
||||
# Settings exposed to the web.
|
||||
settings:
|
||||
posthog:
|
||||
# Whether to enable PostHog in web.
|
||||
enabled: false
|
||||
appcues:
|
||||
# Whether to enable Appcues in web.
|
||||
enabled: false
|
||||
sentry:
|
||||
# Whether to enable Sentry in web.
|
||||
enabled: false
|
||||
pylon:
|
||||
# Whether to enable Pylon in web.
|
||||
enabled: false
|
||||
|
||||
##################### Cache #####################
|
||||
cache:
|
||||
@@ -188,11 +174,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.
|
||||
@@ -432,7 +413,7 @@ cloudintegration:
|
||||
version: v0.0.8
|
||||
|
||||
##################### Trace Detail #####################
|
||||
traces:
|
||||
tracedetail:
|
||||
waterfall:
|
||||
# Number of spans returned per request when the trace is too large to show all at once.
|
||||
span_page_size: 500
|
||||
@@ -448,15 +429,3 @@ authz:
|
||||
openfga:
|
||||
# maximum tuples allowed per openfga write operation.
|
||||
max_tuples_per_write: 300
|
||||
|
||||
##################### Meter Reporter #####################
|
||||
meterreporter:
|
||||
# The interval between collection ticks. Minimum 10m, maximum 24h.
|
||||
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.127.0
|
||||
image: signoz/signoz:v0.122.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
@@ -213,7 +213,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -241,7 +241,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.127.0
|
||||
image: signoz/signoz:v0.122.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
@@ -139,7 +139,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command:
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
replicas: 3
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.144.5
|
||||
image: signoz/signoz-otel-collector:v0.144.3
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_CLUSTER=cluster
|
||||
|
||||
@@ -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.127.0}
|
||||
image: signoz/signoz:${VERSION:-v0.122.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -204,7 +204,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
@@ -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.127.0}
|
||||
image: signoz/signoz:${VERSION:-v0.122.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-otel-collector
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
signoz-telemetrystore-migrator:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.144.3}
|
||||
container_name: signoz-telemetrystore-migrator
|
||||
environment:
|
||||
- SIGNOZ_OTEL_COLLECTOR_CLICKHOUSE_DSN=tcp://clickhouse:9000
|
||||
|
||||
3112
docs/api/openapi.yml
3112
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"
|
||||
}
|
||||
@@ -13,12 +13,13 @@ Before diving in, make sure you have these tools installed:
|
||||
- Download from [go.dev/dl](https://go.dev/dl/)
|
||||
- Check [go.mod](../../go.mod#L3) for the minimum version
|
||||
|
||||
|
||||
- **Node** - Powers our frontend
|
||||
- Download from [nodejs.org](https://nodejs.org)
|
||||
- Check [.nvmrc](../../frontend/.nvmrc) for the version
|
||||
|
||||
- **Pnpm** - Our frontend package manager
|
||||
- Follow the [installation guide](https://pnpm.io/installation)
|
||||
- **Yarn** - Our frontend package manager
|
||||
- Follow the [installation guide](https://yarnpkg.com/getting-started/install)
|
||||
|
||||
- **Docker** - For running Clickhouse and Postgres locally
|
||||
- Get it from [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)
|
||||
@@ -94,7 +95,7 @@ This command:
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
pnpm install
|
||||
yarn install
|
||||
```
|
||||
|
||||
3. Create a `.env` file in this directory:
|
||||
@@ -104,10 +105,10 @@ This command:
|
||||
|
||||
4. Start the development server:
|
||||
```bash
|
||||
pnpm dev
|
||||
yarn dev
|
||||
```
|
||||
|
||||
> 💡 **Tip**: `pnpm dev` will automatically rebuild when you make changes to the code
|
||||
> 💡 **Tip**: `yarn dev` will automatically rebuild when you make changes to the code
|
||||
|
||||
Now you're all set to start developing! Happy coding! 🎉
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ import ec2Url from '@/assets/Logos/ec2.svg';
|
||||
|
||||
1. Add the logo SVG to `src/assets/Logos/` and add a top-level import in the config file (e.g., `import myServiceUrl from '@/assets/Logos/my-service.svg'`)
|
||||
2. Add your data source object to the `onboardingConfigWithLinks` array, referencing the imported variable for `imgUrl`
|
||||
3. Test the flow locally with `pnpm dev`
|
||||
3. Test the flow locally with `yarn dev`
|
||||
4. Validation:
|
||||
- Navigate to the [onboarding page](http://localhost:3301/get-started-with-signoz-cloud) on your local machine
|
||||
- Data source appears in the list
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package fileauditor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
)
|
||||
|
||||
func (provider *provider) export(ctx context.Context, events []audittypes.AuditEvent) error {
|
||||
logs := audittypes.NewPLogsFromAuditEvents(events, "signoz", provider.build.Version(), "signoz.audit")
|
||||
|
||||
payload, err := provider.marshaler.MarshalLogs(logs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Combine the payload and trailing newline into one Write call so the line
|
||||
// is emitted in a single syscall — concurrent readers see either the full
|
||||
// line or nothing, never a torn JSON object.
|
||||
payload = append(payload, '\n')
|
||||
|
||||
provider.mu.Lock()
|
||||
defer provider.mu.Unlock()
|
||||
|
||||
if _, err := provider.file.Write(payload); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "audit export failed", errors.Attr(err), slog.Int("dropped_log_records", len(events)))
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.file.Sync()
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package fileauditor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/auditor"
|
||||
"github.com/SigNoz/signoz/pkg/auditor/auditorserver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"go.opentelemetry.io/collector/pdata/plog"
|
||||
)
|
||||
|
||||
var _ auditor.Auditor = (*provider)(nil)
|
||||
|
||||
type provider struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
config auditor.Config
|
||||
licensing licensing.Licensing
|
||||
build version.Build
|
||||
server *auditorserver.Server
|
||||
marshaler plog.JSONMarshaler
|
||||
file *os.File
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewFactory(licensing licensing.Licensing, build version.Build) factory.ProviderFactory[auditor.Auditor, auditor.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("file"), func(ctx context.Context, providerSettings factory.ProviderSettings, config auditor.Config) (auditor.Auditor, error) {
|
||||
return newProvider(ctx, providerSettings, config, licensing, build)
|
||||
})
|
||||
}
|
||||
|
||||
func newProvider(_ context.Context, providerSettings factory.ProviderSettings, config auditor.Config, licensing licensing.Licensing, build version.Build) (auditor.Auditor, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/auditor/fileauditor")
|
||||
|
||||
file, err := os.OpenFile(config.File.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, auditor.ErrCodeAuditExportFailed, "failed to open audit file %q", config.File.Path)
|
||||
}
|
||||
|
||||
provider := &provider{
|
||||
settings: settings,
|
||||
config: config,
|
||||
licensing: licensing,
|
||||
build: build,
|
||||
marshaler: plog.JSONMarshaler{},
|
||||
file: file,
|
||||
}
|
||||
|
||||
server, err := auditorserver.New(settings,
|
||||
auditorserver.Config{
|
||||
BufferSize: config.BufferSize,
|
||||
BatchSize: config.BatchSize,
|
||||
FlushInterval: config.FlushInterval,
|
||||
},
|
||||
provider.export,
|
||||
)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider.server = server
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Start(ctx context.Context) error {
|
||||
return provider.server.Start(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) Audit(ctx context.Context, event audittypes.AuditEvent) {
|
||||
if event.PrincipalAttributes.PrincipalOrgID.IsZero() {
|
||||
provider.settings.Logger().WarnContext(ctx, "audit event dropped as org_id is zero")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := provider.licensing.GetActive(ctx, event.PrincipalAttributes.PrincipalOrgID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
provider.server.Add(ctx, event)
|
||||
}
|
||||
|
||||
func (provider *provider) Healthy() <-chan struct{} {
|
||||
return provider.server.Healthy()
|
||||
}
|
||||
|
||||
func (provider *provider) Stop(ctx context.Context) error {
|
||||
serverErr := provider.server.Stop(ctx)
|
||||
fileErr := provider.file.Close()
|
||||
if serverErr != nil {
|
||||
return serverErr
|
||||
}
|
||||
|
||||
return fileErr
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*o
|
||||
}
|
||||
|
||||
func (provider *provider) CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error) {
|
||||
tuples, correlations, err := authtypes.NewTuplesFromTransactionsWithCorrelations(transactions, subject, orgID)
|
||||
tuples, err := authtypes.NewTuplesFromTransactions(transactions, subject, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,21 +99,10 @@ func (provider *provider) CheckTransactions(ctx context.Context, subject string,
|
||||
|
||||
results := make([]*authtypes.TransactionWithAuthorization, len(transactions))
|
||||
for i, txn := range transactions {
|
||||
txnID := txn.ID.StringValue()
|
||||
authorized := batchResults[txnID].Authorized
|
||||
|
||||
if !authorized {
|
||||
for _, correlationID := range correlations[txnID] {
|
||||
if result, exists := batchResults[correlationID]; exists && result.Authorized {
|
||||
authorized = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := batchResults[txn.ID.StringValue()]
|
||||
results[i] = &authtypes.TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: authorized,
|
||||
Authorized: result.Authorized,
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
|
||||
@@ -7,27 +7,17 @@ type organization
|
||||
|
||||
type user
|
||||
relations
|
||||
define create: [user, serviceaccount, role#assignee]
|
||||
define list: [user, serviceaccount, role#assignee]
|
||||
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
|
||||
define attach: [user, serviceaccount, role#assignee]
|
||||
define detach: [user, serviceaccount, role#assignee]
|
||||
|
||||
type serviceaccount
|
||||
relations
|
||||
define create: [user, serviceaccount, role#assignee]
|
||||
define list: [user, serviceaccount, role#assignee]
|
||||
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
|
||||
define attach: [user, serviceaccount, role#assignee]
|
||||
define detach: [user, serviceaccount, role#assignee]
|
||||
|
||||
type anonymous
|
||||
|
||||
@@ -35,31 +25,25 @@ type role
|
||||
relations
|
||||
define assignee: [user, serviceaccount, anonymous]
|
||||
|
||||
define create: [user, serviceaccount, role#assignee]
|
||||
define list: [user, serviceaccount, role#assignee]
|
||||
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
|
||||
define attach: [user, serviceaccount, role#assignee]
|
||||
define detach: [user, serviceaccount, role#assignee]
|
||||
|
||||
type metaresource
|
||||
type metaresources
|
||||
relations
|
||||
define create: [user, serviceaccount, role#assignee]
|
||||
define list: [user, serviceaccount, role#assignee]
|
||||
|
||||
define read: [user, serviceaccount, anonymous, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
type metaresource
|
||||
relations
|
||||
define read: [user, serviceaccount, anonymous, role#assignee]
|
||||
define update: [user, serviceaccount, role#assignee]
|
||||
define delete: [user, serviceaccount, role#assignee]
|
||||
|
||||
define attach: [user, serviceaccount, role#assignee]
|
||||
define detach: [user, serviceaccount, role#assignee]
|
||||
|
||||
define block: [user, serviceaccount, role#assignee]
|
||||
define block: [user, serviceaccount, role#assignee]
|
||||
|
||||
|
||||
type telemetryresource
|
||||
relations
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
define read: [user, serviceaccount, role#assignee]
|
||||
@@ -1,61 +0,0 @@
|
||||
// Package staticmetercollector emits a fixed-value meter reading per org per window.
|
||||
package staticmetercollector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/metercollector"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/zeustypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var _ metercollector.MeterCollector = (*Provider)(nil)
|
||||
|
||||
type Provider struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
config metercollector.StaticConfig
|
||||
}
|
||||
|
||||
func NewFactory() factory.ProviderFactory[metercollector.MeterCollector, metercollector.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName(metercollector.ProviderStatic), func(ctx context.Context, providerSettings factory.ProviderSettings, config metercollector.Config) (metercollector.MeterCollector, error) {
|
||||
return newProvider(providerSettings, config.Static), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func newProvider(providerSettings factory.ProviderSettings, config metercollector.StaticConfig) *Provider {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/metercollector/staticmetercollector")
|
||||
|
||||
return &Provider{
|
||||
settings: settings,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *Provider) Name() zeustypes.MeterName { return provider.config.Name }
|
||||
func (provider *Provider) Unit() zeustypes.MeterUnit { return provider.config.Unit }
|
||||
func (provider *Provider) Aggregation() zeustypes.MeterAggregation {
|
||||
return provider.config.Aggregation
|
||||
}
|
||||
|
||||
func (provider *Provider) Origin(_ context.Context, _ valuer.UUID, license *licensetypes.License, _ time.Time) (time.Time, error) {
|
||||
if license == nil || license.CreatedAt.IsZero() {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
createdAt := license.CreatedAt.UTC()
|
||||
return time.Date(createdAt.Year(), createdAt.Month(), createdAt.Day(), 0, 0, 0, 0, time.UTC), nil
|
||||
}
|
||||
|
||||
func (provider *Provider) Collect(_ context.Context, orgID valuer.UUID, license *licensetypes.License, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
|
||||
if license == nil || license.Key == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []zeustypes.Meter{
|
||||
zeustypes.NewMeter(provider.config.Name, provider.config.Value, provider.config.Unit, provider.config.Aggregation, window, zeustypes.NewDimensions(zeustypes.OrganizationID.String(orgID.StringValue()))),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
// Package telemetrymetercollector collects telemetry meters (logs, traces, metrics)
|
||||
// by retention. One Provider materializes per TelemetryConfig.
|
||||
package telemetrymetercollector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/metercollector"
|
||||
"github.com/SigNoz/signoz/pkg/modules/retention"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrymeter"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/zeustypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var (
|
||||
labelKeyPattern = regexp.MustCompile(`^[A-Za-z0-9_.\-]+$`)
|
||||
labelValuePattern = regexp.MustCompile(`^[A-Za-z0-9_.\-:]+$`)
|
||||
)
|
||||
|
||||
var _ metercollector.MeterCollector = (*Provider)(nil)
|
||||
|
||||
type Provider struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
config metercollector.TelemetryConfig
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
retentionGetter retention.Getter
|
||||
}
|
||||
|
||||
func NewFactory(telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) factory.ProviderFactory[metercollector.MeterCollector, metercollector.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName(metercollector.ProviderTelemetry), func(ctx context.Context, providerSettings factory.ProviderSettings, config metercollector.Config) (metercollector.MeterCollector, error) {
|
||||
return newProvider(providerSettings, config.Telemetry, telemetryStore, retentionGetter), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func newProvider(
|
||||
providerSettings factory.ProviderSettings,
|
||||
config metercollector.TelemetryConfig,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
retentionGetter retention.Getter,
|
||||
) *Provider {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/metercollector/telemetrymetercollector")
|
||||
|
||||
return &Provider{
|
||||
settings: settings,
|
||||
config: config,
|
||||
telemetryStore: telemetryStore,
|
||||
retentionGetter: retentionGetter,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *Provider) Name() zeustypes.MeterName { return provider.config.Name }
|
||||
func (provider *Provider) Unit() zeustypes.MeterUnit { return provider.config.Unit }
|
||||
func (provider *Provider) Aggregation() zeustypes.MeterAggregation {
|
||||
return provider.config.Aggregation
|
||||
}
|
||||
|
||||
func (provider *Provider) Origin(ctx context.Context, _ valuer.UUID, _ *licensetypes.License, todayStart time.Time) (time.Time, error) {
|
||||
query, args := buildOriginQuery(provider.config.Name.String())
|
||||
|
||||
var minMs int64
|
||||
if err := provider.telemetryStore.ClickhouseDB().QueryRow(ctx, query, args...).Scan(&minMs); err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
if minMs == 0 {
|
||||
return todayStart, nil
|
||||
}
|
||||
|
||||
minDay := time.UnixMilli(minMs).UTC()
|
||||
return time.Date(minDay.Year(), minDay.Month(), minDay.Day(), 0, 0, 0, 0, time.UTC), nil
|
||||
}
|
||||
|
||||
func (provider *Provider) Collect(
|
||||
ctx context.Context,
|
||||
orgID valuer.UUID,
|
||||
_ *licensetypes.License,
|
||||
window zeustypes.MeterWindow,
|
||||
) ([]zeustypes.Meter, error) {
|
||||
meterName := provider.config.Name.String()
|
||||
|
||||
segments, err := provider.retentionGetter.GetRetentionPolicySegments(
|
||||
ctx,
|
||||
orgID,
|
||||
provider.config.DBName,
|
||||
provider.config.TableName,
|
||||
provider.config.DefaultRetentionDays,
|
||||
window.StartUnixMilli,
|
||||
window.EndUnixMilli,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valuesByRetentionDays := make(map[int]int64)
|
||||
|
||||
for _, segment := range segments {
|
||||
query, args, err := buildQuery(meterName, segment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := provider.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := func() error {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var retentionDays int32
|
||||
var value int64
|
||||
|
||||
if err := rows.Scan(&retentionDays, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valuesByRetentionDays[int(retentionDays)] += value
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
meters := make([]zeustypes.Meter, 0, len(valuesByRetentionDays))
|
||||
for retentionDays, value := range valuesByRetentionDays {
|
||||
meters = append(meters, zeustypes.NewMeter(provider.config.Name, value, provider.config.Unit, provider.config.Aggregation, window, buildDimensions(orgID, retentionDays)))
|
||||
}
|
||||
|
||||
// Empty windows still emit a sentinel so checkpoints can advance.
|
||||
if len(meters) == 0 && len(segments) > 0 {
|
||||
meters = append(meters, zeustypes.NewMeter(provider.config.Name, 0, provider.config.Unit, provider.config.Aggregation, window, buildDimensions(orgID, segments[len(segments)-1].DefaultDays)))
|
||||
}
|
||||
|
||||
return meters, nil
|
||||
}
|
||||
|
||||
func buildOriginQuery(meterName string) (string, []any) {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select("toInt64(ifNull(min(unix_milli), 0))")
|
||||
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
|
||||
sb.Where(sb.Equal("metric_name", meterName))
|
||||
return sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
}
|
||||
|
||||
func buildQuery(meterName string, segment *retentiontypes.RetentionPolicySegment) (string, []any, error) {
|
||||
retentionExpr, err := buildRetentionMultiIfSQL(segment.Rules, segment.DefaultDays)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
selects := []string{
|
||||
retentionExpr + " AS retention_days",
|
||||
"toInt64(ifNull(sum(value), 0)) AS value",
|
||||
}
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
sb.Select(selects...)
|
||||
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
|
||||
sb.Where(
|
||||
sb.Equal("metric_name", meterName),
|
||||
sb.GTE("unix_milli", segment.StartMs),
|
||||
sb.LT("unix_milli", segment.EndMs),
|
||||
)
|
||||
sb.GroupBy("retention_days")
|
||||
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
return query, args, nil
|
||||
}
|
||||
|
||||
func buildRetentionMultiIfSQL(rules []retentiontypes.CustomRetentionRule, defaultDays int) (string, error) {
|
||||
if defaultDays <= 0 {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, metercollector.ErrCodeMeterCollectorInvalidCustomRetentionRule, "non-positive default retention %d", defaultDays)
|
||||
}
|
||||
|
||||
if len(rules) == 0 {
|
||||
return "toInt32(" + strconv.Itoa(defaultDays) + ")", nil
|
||||
}
|
||||
|
||||
arms := make([]string, 0, 2*len(rules)+1)
|
||||
for ruleIndex, rule := range rules {
|
||||
if rule.TTLDays <= 0 {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, metercollector.ErrCodeMeterCollectorInvalidCustomRetentionRule, "rule %d has non-positive ttl_days %d", ruleIndex, rule.TTLDays)
|
||||
}
|
||||
conditionExpr, err := buildRuleConditionSQL(ruleIndex, rule)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
arms = append(arms, conditionExpr)
|
||||
arms = append(arms, strconv.Itoa(rule.TTLDays))
|
||||
}
|
||||
arms = append(arms, strconv.Itoa(defaultDays))
|
||||
|
||||
return "toInt32(multiIf(" + strings.Join(arms, ", ") + "))", nil
|
||||
}
|
||||
|
||||
func buildRuleConditionSQL(ruleIndex int, rule retentiontypes.CustomRetentionRule) (string, error) {
|
||||
if len(rule.Filters) == 0 {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, metercollector.ErrCodeMeterCollectorInvalidCustomRetentionRule, "rule %d has no filters", ruleIndex)
|
||||
}
|
||||
|
||||
filterExprs := make([]string, 0, len(rule.Filters))
|
||||
for filterIndex, filter := range rule.Filters {
|
||||
if !labelKeyPattern.MatchString(filter.Key) {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, metercollector.ErrCodeMeterCollectorInvalidCustomRetentionRule, "rule %d filter %d has invalid key %q", ruleIndex, filterIndex, filter.Key)
|
||||
}
|
||||
if len(filter.Values) == 0 {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, metercollector.ErrCodeMeterCollectorInvalidCustomRetentionRule, "rule %d filter %d has no values", ruleIndex, filterIndex)
|
||||
}
|
||||
|
||||
quoted := make([]string, len(filter.Values))
|
||||
for valueIndex, value := range filter.Values {
|
||||
if !labelValuePattern.MatchString(value) {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, metercollector.ErrCodeMeterCollectorInvalidCustomRetentionRule, "rule %d filter %d value %d is invalid %q", ruleIndex, filterIndex, valueIndex, value)
|
||||
}
|
||||
quoted[valueIndex] = "'" + value + "'"
|
||||
}
|
||||
|
||||
filterExprs = append(filterExprs, fmt.Sprintf("JSONExtractString(labels, '%s') IN (%s)", filter.Key, strings.Join(quoted, ", ")))
|
||||
}
|
||||
|
||||
return strings.Join(filterExprs, " AND "), nil
|
||||
}
|
||||
|
||||
func buildDimensions(orgID valuer.UUID, retentionDays int) map[string]string {
|
||||
retentionDurationSeconds := int64(retentionDays) * 24 * 60 * 60 // seconds
|
||||
|
||||
return zeustypes.NewDimensions(
|
||||
zeustypes.OrganizationID.String(orgID.StringValue()),
|
||||
zeustypes.RetentionDuration.String(strconv.FormatInt(retentionDurationSeconds, 10)),
|
||||
)
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
package httpmeterreporter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/metercollector"
|
||||
"github.com/SigNoz/signoz/pkg/meterreporter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/zeustypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var _ factory.ServiceWithHealthy = (*Provider)(nil)
|
||||
|
||||
type Provider struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
config meterreporter.Config
|
||||
collectorsByName map[zeustypes.MeterName]metercollector.MeterCollector
|
||||
flagger flagger.Flagger
|
||||
licensing licensing.Licensing
|
||||
orgGetter organization.Getter
|
||||
zeus zeus.Zeus
|
||||
healthyC chan struct{}
|
||||
stopC chan struct{}
|
||||
metrics *reporterMetrics
|
||||
}
|
||||
|
||||
func NewFactory(collectorFactories factory.NamedMap[factory.ProviderFactory[metercollector.MeterCollector, metercollector.Config]], collectorConfigs []metercollector.Config, flagger flagger.Flagger, licensing licensing.Licensing, orgGetter organization.Getter, zeus zeus.Zeus) factory.ProviderFactory[meterreporter.Reporter, meterreporter.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("http"), func(ctx context.Context, providerSettings factory.ProviderSettings, config meterreporter.Config) (meterreporter.Reporter, error) {
|
||||
return newProvider(ctx, providerSettings, config, collectorFactories, collectorConfigs, flagger, licensing, orgGetter, zeus)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func newProvider(
|
||||
ctx context.Context,
|
||||
providerSettings factory.ProviderSettings,
|
||||
config meterreporter.Config,
|
||||
collectorFactories factory.NamedMap[factory.ProviderFactory[metercollector.MeterCollector, metercollector.Config]],
|
||||
collectorConfigs []metercollector.Config,
|
||||
flagger flagger.Flagger,
|
||||
licensing licensing.Licensing,
|
||||
orgGetter organization.Getter,
|
||||
zeus zeus.Zeus,
|
||||
) (*Provider, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/meterreporter/httpmeterreporter")
|
||||
|
||||
collectorsByName := map[zeustypes.MeterName]metercollector.MeterCollector{}
|
||||
for _, collectorConfig := range collectorConfigs {
|
||||
collector, err := factory.NewProviderFromNamedMap(ctx, providerSettings, collectorConfig, collectorFactories, collectorConfig.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, exists := collectorsByName[collector.Name()]; exists {
|
||||
return nil, errors.Newf(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "duplicate meter collector %q", collector.Name())
|
||||
}
|
||||
collectorsByName[collector.Name()] = collector
|
||||
}
|
||||
|
||||
metrics, err := newReporterMetrics(settings.Meter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
settings: settings,
|
||||
config: config,
|
||||
collectorsByName: collectorsByName,
|
||||
flagger: flagger,
|
||||
licensing: licensing,
|
||||
orgGetter: orgGetter,
|
||||
zeus: zeus,
|
||||
healthyC: make(chan struct{}),
|
||||
stopC: make(chan struct{}),
|
||||
metrics: metrics,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) Start(ctx context.Context) error {
|
||||
close(provider.healthyC)
|
||||
|
||||
startDelay := provider.config.NewJitter()
|
||||
|
||||
timer := time.NewTimer(startDelay)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-provider.stopC:
|
||||
return nil
|
||||
case <-timer.C:
|
||||
provider.collect(ctx)
|
||||
next := provider.config.Interval - provider.config.NewJitter()
|
||||
timer.Reset(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *Provider) collect(ctx context.Context) {
|
||||
ctx, span := provider.settings.Tracer().Start(ctx, "meterreporter.Collect", trace.WithAttributes(attribute.String("meterreporter.provider", "http")))
|
||||
defer span.End()
|
||||
|
||||
orgs, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to get orgs data", errors.Attr(err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, org := range orgs {
|
||||
evalCtx := featuretypes.NewFlaggerEvaluationContext(org.ID)
|
||||
if !provider.flagger.BooleanOrEmpty(ctx, flagger.FeatureUseMeterReporter, evalCtx) {
|
||||
provider.settings.Logger().DebugContext(ctx, "meter reporter disabled for org, skipping reporting", slog.String("org_id", org.ID.StringValue()))
|
||||
continue
|
||||
}
|
||||
|
||||
license, err := provider.licensing.GetActive(ctx, org.ID)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
provider.settings.Logger().DebugContext(ctx, "no active license found for org, skipping reporting", slog.String("org_id", org.ID.StringValue()))
|
||||
continue
|
||||
}
|
||||
|
||||
span.RecordError(err)
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to fetch active license for org", errors.Attr(err), slog.String("org_id", org.ID.StringValue()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := provider.collectOrg(ctx, org, license); err != nil {
|
||||
span.RecordError(err)
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to collect meters", errors.Attr(err), slog.String("org_id", org.ID.StringValue()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *Provider) Stop(ctx context.Context) error {
|
||||
close(provider.stopC)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Provider) Healthy() <-chan struct{} {
|
||||
return provider.healthyC
|
||||
}
|
||||
|
||||
func (provider *Provider) collectOrg(ctx context.Context, org *types.Organization, license *licensetypes.License) error {
|
||||
now := time.Now().UTC()
|
||||
// Use one timestamp so a tick cannot straddle midnight.
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
|
||||
|
||||
if provider.config.Backfill {
|
||||
checkpointsByMeter, err := provider.checkpoints(ctx, license.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nextByCollector := provider.nextDays(license, todayStart, checkpointsByMeter)
|
||||
|
||||
start, end, ok := backfillRange(nextByCollector, todayStart)
|
||||
if ok {
|
||||
for day := start; !day.After(end); day = day.AddDate(0, 0, 1) {
|
||||
eligible := eligibleCollectors(provider.collectorsByName, nextByCollector, day)
|
||||
if len(eligible) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
window, err := zeustypes.NewMeterWindow(day.UnixMilli(), day.AddDate(0, 0, 1).UnixMilli(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.report(ctx, org.ID, license, window, eligible); err != nil {
|
||||
provider.settings.Logger().WarnContext(ctx, "failed to backfill for day", errors.Attr(err), slog.String("date", day.Format("2006-01-02")))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Today's partial window: every collector is always eligible (next <= today).
|
||||
if now.UnixMilli() > todayStart.UnixMilli() {
|
||||
todayWindow, err := zeustypes.NewMeterWindow(todayStart.UnixMilli(), now.UnixMilli(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.report(ctx, org.ID, license, todayWindow, provider.collectorsByName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Provider) checkpoints(ctx context.Context, licenseKey string) (map[string]time.Time, error) {
|
||||
list, err := provider.zeus.ListMeterCheckpoints(ctx, licenseKey)
|
||||
if err != nil {
|
||||
provider.metrics.checkpoints.Add(ctx, 1, metric.WithAttributes(errors.TypeAttr(err)))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider.metrics.checkpoints.Add(ctx, 1)
|
||||
|
||||
checkpointsByMeter := make(map[string]time.Time, len(list))
|
||||
for _, checkpoint := range list {
|
||||
checkpointsByMeter[checkpoint.Name] = checkpoint.StartDate.UTC()
|
||||
}
|
||||
|
||||
return checkpointsByMeter, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) nextDays(license *licensetypes.License, todayStart time.Time, checkpointsByMeter map[string]time.Time) map[zeustypes.MeterName]time.Time {
|
||||
nextByCollector := make(map[zeustypes.MeterName]time.Time, len(provider.collectorsByName))
|
||||
licenseCreatedAt := license.CreatedAt.UTC()
|
||||
licenseCreatedAtDay := time.Date(licenseCreatedAt.Year(), licenseCreatedAt.Month(), licenseCreatedAt.Day(), 0, 0, 0, 0, time.UTC)
|
||||
|
||||
for _, collector := range provider.collectorsByName {
|
||||
checkpoint, hasCheckpoint := checkpointsByMeter[collector.Name().String()]
|
||||
nextByCollector[collector.Name()] = nextReportableDay(licenseCreatedAtDay, todayStart, checkpoint, hasCheckpoint)
|
||||
}
|
||||
|
||||
return nextByCollector
|
||||
}
|
||||
|
||||
func nextReportableDay(licenseCreatedAtDay time.Time, todayStart time.Time, checkpoint time.Time, hasCheckpoint bool) time.Time {
|
||||
next := licenseCreatedAtDay
|
||||
if next.IsZero() {
|
||||
next = todayStart
|
||||
}
|
||||
|
||||
if hasCheckpoint {
|
||||
checkpointNext := checkpoint.AddDate(0, 0, 1)
|
||||
if checkpointNext.After(next) {
|
||||
next = checkpointNext
|
||||
}
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
func (provider *Provider) report(ctx context.Context, orgID valuer.UUID, license *licensetypes.License, window zeustypes.MeterWindow, collectors map[zeustypes.MeterName]metercollector.MeterCollector) error {
|
||||
date := time.UnixMilli(window.StartUnixMilli).UTC().Format("2006-01-02")
|
||||
|
||||
meters := make([]zeustypes.Meter, 0, len(collectors))
|
||||
for _, collector := range collectors {
|
||||
meterAttr := attribute.String("signoz.meter.name", collector.Name().String())
|
||||
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
|
||||
}
|
||||
|
||||
provider.metrics.collections.Add(ctx, 1, metric.WithAttributes(meterAttr))
|
||||
meters = append(meters, collectedReadings...)
|
||||
}
|
||||
|
||||
if len(meters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
idempotencyKey := fmt.Sprintf("meterreporter:%s", date)
|
||||
|
||||
body, err := json.Marshal(meters)
|
||||
if err != nil {
|
||||
provider.metrics.reports.Add(ctx, 1, metric.WithAttributes(errors.TypeAttr(err)))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.zeus.PutMetersV3(ctx, license.Key, idempotencyKey, body); err != nil {
|
||||
provider.metrics.reports.Add(ctx, 1, metric.WithAttributes(errors.TypeAttr(err)))
|
||||
return err
|
||||
}
|
||||
|
||||
provider.metrics.reports.Add(ctx, 1)
|
||||
provider.metrics.meters.Add(ctx, int64(len(meters)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// backfillRange returns the inclusive sealed-day range ending at yesterday.
|
||||
func backfillRange(nextByCollector map[zeustypes.MeterName]time.Time, todayStart time.Time) (start, end time.Time, ok bool) {
|
||||
yesterday := todayStart.AddDate(0, 0, -1)
|
||||
|
||||
for _, next := range nextByCollector {
|
||||
if !next.Before(todayStart) {
|
||||
continue
|
||||
}
|
||||
if start.IsZero() || next.Before(start) {
|
||||
start = next
|
||||
}
|
||||
}
|
||||
|
||||
if start.IsZero() || start.After(yesterday) {
|
||||
return time.Time{}, time.Time{}, false
|
||||
}
|
||||
|
||||
return start, yesterday, true
|
||||
}
|
||||
|
||||
func eligibleCollectors(collectors map[zeustypes.MeterName]metercollector.MeterCollector, nextByCollector map[zeustypes.MeterName]time.Time, day time.Time) map[zeustypes.MeterName]metercollector.MeterCollector {
|
||||
eligible := make(map[zeustypes.MeterName]metercollector.MeterCollector, len(collectors))
|
||||
for name, collector := range collectors {
|
||||
if !nextByCollector[name].After(day) {
|
||||
eligible[name] = collector
|
||||
}
|
||||
}
|
||||
|
||||
return eligible
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package httpmeterreporter
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
)
|
||||
|
||||
type reporterMetrics struct {
|
||||
checkpoints metric.Int64Counter
|
||||
reports metric.Int64Counter
|
||||
collections metric.Int64Counter
|
||||
meters metric.Int64Counter
|
||||
}
|
||||
|
||||
func newReporterMetrics(meter metric.Meter) (*reporterMetrics, error) {
|
||||
var errs error
|
||||
|
||||
checkpoints, err := meter.Int64Counter("signoz.meterreporter.checkpoints", metric.WithDescription("Zeus meter checkpoint fetches."), metric.WithUnit("{checkpoint}"))
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
reports, err := meter.Int64Counter("signoz.meterreporter.reports", metric.WithDescription("Meter reports shipped to Zeus."), metric.WithUnit("{report}"))
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
collections, err := meter.Int64Counter("signoz.meterreporter.collections", metric.WithDescription("Per-meter collect calls."), metric.WithUnit("{collection}"))
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
meters, err := meter.Int64Counter("signoz.meterreporter.meters", metric.WithDescription("Meter readings shipped to Zeus."), metric.WithUnit("{meter}"))
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return &reporterMetrics{
|
||||
checkpoints: checkpoints,
|
||||
reports: reports,
|
||||
collections: collections,
|
||||
meters: meters,
|
||||
}, nil
|
||||
}
|
||||
@@ -54,6 +54,11 @@ func (provider *awscloudprovider) GetServiceDefinition(ctx context.Context, serv
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// override cloud integration dashboard id
|
||||
for index, dashboard := range serviceDef.Assets.Dashboards {
|
||||
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAWS, serviceID.StringValue(), dashboard.ID)
|
||||
}
|
||||
|
||||
return serviceDef, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, se
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// override cloud integration dashboard id.
|
||||
for index, dashboard := range serviceDef.Assets.Dashboards {
|
||||
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
|
||||
}
|
||||
|
||||
return serviceDef, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package implcloudintegration
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -10,7 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
type module struct {
|
||||
store cloudintegrationtypes.Store
|
||||
dashboardModule dashboard.Module
|
||||
gateway gateway.Gateway
|
||||
zeus zeus.Zeus
|
||||
licensing licensing.Licensing
|
||||
@@ -35,7 +34,6 @@ type module struct {
|
||||
|
||||
func NewModule(
|
||||
store cloudintegrationtypes.Store,
|
||||
dashboardModule dashboard.Module,
|
||||
global global.Global,
|
||||
zeus zeus.Zeus,
|
||||
gateway gateway.Gateway,
|
||||
@@ -46,7 +44,6 @@ func NewModule(
|
||||
) (cloudintegration.Module, error) {
|
||||
return &module{
|
||||
store: store,
|
||||
dashboardModule: dashboardModule,
|
||||
global: global,
|
||||
zeus: zeus,
|
||||
gateway: gateway,
|
||||
@@ -257,41 +254,7 @@ func (module *module) DisconnectAccount(ctx context.Context, orgID valuer.UUID,
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
services, err := module.store.ListServices(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sharedServices, err := module.store.ListSharedServices(ctx, orgID, provider, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, svc := range services {
|
||||
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, svc.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !svcCfg.IsMetricsEnabled(provider) {
|
||||
continue
|
||||
}
|
||||
|
||||
if cloudintegrationtypes.IsServiceSharedWithMetricsEnabled(provider, sharedServices[svc.Type]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := module.deprovisionDashboards(ctx, orgID, provider, svc.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := module.store.DeleteServicesByCloudIntegrationID(ctx, orgID, accountID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.RemoveAccount(ctx, orgID, accountID, provider)
|
||||
})
|
||||
return module.store.RemoveAccount(ctx, orgID, accountID, provider)
|
||||
}
|
||||
|
||||
func (module *module) ListServicesMetadata(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, integrationID valuer.UUID) ([]*cloudintegrationtypes.ServiceMetadata, error) {
|
||||
@@ -368,16 +331,12 @@ func (module *module) GetService(ctx context.Context, orgID valuer.UUID, service
|
||||
|
||||
integrationService = cloudintegrationtypes.NewCloudIntegrationServiceFromStorable(storedService, serviceConfig)
|
||||
}
|
||||
|
||||
if err := module.enrichDashboardIDs(ctx, orgID, provider, serviceID, serviceDefinition); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cloudintegrationtypes.NewService(*serviceDefinition, integrationService), nil
|
||||
}
|
||||
|
||||
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
@@ -398,21 +357,10 @@ func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, crea
|
||||
return err
|
||||
}
|
||||
|
||||
metricsEnabled := service.Config.IsMetricsEnabled(provider)
|
||||
storableService := cloudintegrationtypes.NewStorableCloudIntegrationService(service, string(configJSON))
|
||||
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.CreateService(ctx, storableService); err != nil {
|
||||
return err
|
||||
}
|
||||
if metricsEnabled {
|
||||
return module.provisionDashboards(ctx, orgID, createdBy, creator, provider, service, serviceDefinition)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return module.store.CreateService(ctx, cloudintegrationtypes.NewStorableCloudIntegrationService(service, string(configJSON)))
|
||||
}
|
||||
|
||||
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, integrationService *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, integrationService *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
@@ -433,28 +381,43 @@ func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, crea
|
||||
return err
|
||||
}
|
||||
|
||||
metricsEnabled := integrationService.Config.IsMetricsEnabled(provider)
|
||||
storableService := cloudintegrationtypes.NewStorableCloudIntegrationService(integrationService, string(configJSON))
|
||||
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.UpdateService(ctx, storableService); err != nil {
|
||||
return err
|
||||
}
|
||||
return module.store.UpdateService(ctx, storableService)
|
||||
}
|
||||
|
||||
if metricsEnabled {
|
||||
return module.provisionDashboards(ctx, orgID, createdBy, creator, provider, integrationService, serviceDefinition)
|
||||
}
|
||||
func (module *module) GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
sharedServices, err := module.store.ListSharedServices(ctx, orgID, provider, integrationService.CloudIntegrationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cloudintegrationtypes.IsServiceSharedWithMetricsEnabled(provider, sharedServices[integrationService.Type]) {
|
||||
return nil
|
||||
}
|
||||
_, _, _, err = cloudintegrationtypes.ParseCloudIntegrationDashboardID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return module.deprovisionDashboards(ctx, orgID, provider, integrationService.Type)
|
||||
})
|
||||
allDashboards, err := module.listDashboards(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range allDashboards {
|
||||
if d.ID == id {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration dashboard not found")
|
||||
}
|
||||
|
||||
func (module *module) ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return module.listDashboards(ctx, orgID)
|
||||
}
|
||||
|
||||
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
|
||||
@@ -530,73 +493,52 @@ func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID,
|
||||
return factorAPIKey.Key, nil
|
||||
}
|
||||
|
||||
// provisionDashboards creates dashboard and integration_dashboard rows for each dashboard in the service definition.
|
||||
// Must be called within a transaction (ctx carries the tx).
|
||||
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)
|
||||
func (module *module) listDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
var allDashboards []*dashboardtypes.Dashboard
|
||||
|
||||
existing, err := module.store.GetIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
if existing != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
createdDashboard, err := module.dashboardModule.Create(ctx, orgID, createdBy, creator, dashboardtypes.SourceIntegration, dashboardtypes.PostableDashboard(dashboard.Definition))
|
||||
for provider := range module.cloudProvidersMap {
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
integrationDashboard := cloudintegrationtypes.NewStorableIntegrationDashboard(createdDashboard.ID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err := module.store.CreateIntegrationDashboard(ctx, integrationDashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
rows, err := module.store.ListIntegrationDashboardsBySlugPrefix(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slugPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
dashID, err := valuer.NewUUID(row.DashboardID)
|
||||
connectedAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := module.store.DeleteIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, row.Slug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.dashboardModule.DeleteUnsafe(ctx, orgID, dashID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// enrichDashboardIDs replaces the raw dashboard name in each Dashboard.ID with the provisioned UUID.
|
||||
// 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)
|
||||
row, err := module.store.GetIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
continue
|
||||
for _, storableAccount := range connectedAccounts {
|
||||
storedServices, err := module.store.ListServices(ctx, storableAccount.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, storedSvc := range storedServices {
|
||||
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, storedSvc.Config)
|
||||
if err != nil || !serviceConfig.IsMetricsEnabled(provider) {
|
||||
continue
|
||||
}
|
||||
|
||||
svcDef, err := cloudProvider.GetServiceDefinition(ctx, storedSvc.Type)
|
||||
if err != nil || svcDef == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dashboards := cloudintegrationtypes.GetDashboardsFromAssets(
|
||||
storedSvc.Type.StringValue(),
|
||||
orgID,
|
||||
provider,
|
||||
storableAccount.CreatedAt,
|
||||
svcDef.Assets,
|
||||
)
|
||||
allDashboards = append(allDashboards, dashboards...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
serviceDefinition.Assets.Dashboards[i].ID = row.DashboardID
|
||||
}
|
||||
return nil
|
||||
|
||||
sort.Slice(allDashboards, func(i, j int) bool {
|
||||
return allDashboards[i].ID < allDashboards[j].ID
|
||||
})
|
||||
|
||||
return allDashboards, nil
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -50,14 +49,6 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
dashboard, err := module.Get(ctx, orgID, publicDashboard.DashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dashboard.ErrIfNotPublishable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storablePublicDashboard, err := module.store.GetPublic(ctx, publicDashboard.DashboardID.StringValue())
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
@@ -138,14 +129,6 @@ func (module *module) UpdatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
dashboard, err := module.Get(ctx, orgID, publicDashboard.DashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dashboard.ErrIfNotPublishable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.UpdatePublic(ctx, dashboardtypes.NewStorablePublicDashboardFromPublicDashboard(publicDashboard))
|
||||
}
|
||||
|
||||
@@ -155,19 +138,28 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dashboard.ErrIfNotDeletable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashboard.Locked {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
|
||||
}
|
||||
|
||||
return module.delete(ctx, orgID, id)
|
||||
}
|
||||
err = module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
err := module.store.DeletePublic(ctx, id.String())
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (module *module) DeleteUnsafe(ctx context.Context, orgID, id valuer.UUID) error {
|
||||
return module.delete(ctx, orgID, id)
|
||||
err = module.store.Delete(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
@@ -176,14 +168,6 @@ func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashb
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
dashboard, err := module.Get(ctx, orgID, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dashboard.ErrIfNotPublishable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.DeletePublic(ctx, dashboardID.StringValue())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -209,16 +193,8 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
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) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Create(ctx, orgID, createdBy, creator, data)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
@@ -240,12 +216,3 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error {
|
||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, isAdmin, lock)
|
||||
}
|
||||
|
||||
func (module *module) delete(ctx context.Context, orgID, id valuer.UUID) error {
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.DeletePublic(ctx, id.String()); err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
return module.store.Delete(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(),
|
||||
|
||||
@@ -80,24 +80,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
fineGrainedAuthz := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureUseFineGrainedAuthz, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureUseFineGrainedAuthz.String()),
|
||||
Active: fineGrainedAuthz,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
|
||||
@@ -34,7 +35,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),
|
||||
@@ -49,7 +49,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
rules = append(rules, tr)
|
||||
|
||||
// create ch rule task for evaluation
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc)
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
|
||||
} else if opts.Rule.RuleType == ruletypes.RuleTypeProm {
|
||||
|
||||
@@ -60,7 +60,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),
|
||||
@@ -74,7 +73,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
rules = append(rules, pr)
|
||||
|
||||
// create promql rule task for evaluation
|
||||
task = newTask(baserules.TaskTypeProm, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc)
|
||||
task = newTask(baserules.TaskTypeProm, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
|
||||
} else if opts.Rule.RuleType == ruletypes.RuleTypeAnomaly {
|
||||
// create anomaly rule
|
||||
@@ -84,7 +83,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),
|
||||
@@ -98,7 +96,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
rules = append(rules, ar)
|
||||
|
||||
// create anomaly rule task for evaluation
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc)
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
|
||||
} else {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, ruletypes.RuleTypeProm, ruletypes.RuleTypeThreshold)
|
||||
@@ -144,7 +142,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 +163,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 +182,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),
|
||||
@@ -215,9 +210,9 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
|
||||
}
|
||||
|
||||
// newTask returns an appropriate group for the rule type
|
||||
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc) baserules.Task {
|
||||
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, maintenanceStore ruletypes.MaintenanceStore, orgID valuer.UUID) baserules.Task {
|
||||
if taskType == baserules.TaskTypeCh {
|
||||
return baserules.NewRuleTask(name, "", frequency, rules, opts, notify)
|
||||
return baserules.NewRuleTask(name, "", frequency, rules, opts, notify, maintenanceStore, orgID)
|
||||
}
|
||||
return baserules.NewPromRuleTask(name, "", frequency, rules, opts, notify)
|
||||
return baserules.NewPromRuleTask(name, "", frequency, rules, opts, notify, maintenanceStore, orgID)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -26,7 +25,7 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cmock "github.com/SigNoz/clickhouse-go-mock"
|
||||
cmock "github.com/srikanthccv/ClickHouse-go-mock"
|
||||
)
|
||||
|
||||
func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
|
||||
@@ -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) {
|
||||
|
||||
@@ -150,72 +150,6 @@ func (provider *Provider) PutMetersV2(ctx context.Context, key string, data []by
|
||||
return err
|
||||
}
|
||||
|
||||
func (provider *Provider) PutMetersV3(ctx context.Context, key string, idempotencyKey string, data []byte) error {
|
||||
headers := http.Header{}
|
||||
if idempotencyKey != "" {
|
||||
headers.Set("X-Idempotency-Key", idempotencyKey)
|
||||
}
|
||||
|
||||
_, err := provider.doWithHeaders(
|
||||
ctx,
|
||||
provider.config.URL.JoinPath("/v2/meters"),
|
||||
http.MethodPost,
|
||||
key,
|
||||
data,
|
||||
headers,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (provider *Provider) ListMeterCheckpoints(ctx context.Context, key string) ([]zeustypes.MeterCheckpoint, error) {
|
||||
response, err := provider.do(
|
||||
ctx,
|
||||
provider.config.URL.JoinPath("/v2/meters/checkpoints"),
|
||||
http.MethodGet,
|
||||
key,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checkpointValues := gjson.GetBytes(response, "data")
|
||||
if !checkpointValues.Exists() || checkpointValues.Type == gjson.Null {
|
||||
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoints are required")
|
||||
}
|
||||
|
||||
if !checkpointValues.IsArray() {
|
||||
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoints must be an array")
|
||||
}
|
||||
|
||||
checkpointResults := checkpointValues.Array()
|
||||
checkpoints := make([]zeustypes.MeterCheckpoint, 0, len(checkpointResults))
|
||||
for _, checkpointValue := range checkpointResults {
|
||||
name := checkpointValue.Get("name").String()
|
||||
if name == "" {
|
||||
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoint name is required")
|
||||
}
|
||||
|
||||
startDateString := checkpointValue.Get("start_date").String()
|
||||
if startDateString == "" {
|
||||
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoint start_date is required for %q", name)
|
||||
}
|
||||
|
||||
startDate, err := time.Parse("2006-01-02", startDateString)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, zeus.ErrCodeResponseMalformed, "parse meter checkpoint start_date %q for %q", startDateString, name)
|
||||
}
|
||||
|
||||
checkpoints = append(checkpoints, zeustypes.MeterCheckpoint{
|
||||
Name: name,
|
||||
StartDate: startDate,
|
||||
})
|
||||
}
|
||||
|
||||
return checkpoints, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) PutProfile(ctx context.Context, key string, profile *zeustypes.PostableProfile) error {
|
||||
body, err := json.Marshal(profile)
|
||||
if err != nil {
|
||||
@@ -251,21 +185,12 @@ func (provider *Provider) PutHost(ctx context.Context, key string, host *zeustyp
|
||||
}
|
||||
|
||||
func (provider *Provider) do(ctx context.Context, url *url.URL, method string, key string, requestBody []byte) ([]byte, error) {
|
||||
return provider.doWithHeaders(ctx, url, method, key, requestBody, nil)
|
||||
}
|
||||
|
||||
func (provider *Provider) doWithHeaders(ctx context.Context, url *url.URL, method string, key string, requestBody []byte, extraHeaders http.Header) ([]byte, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, method, url.String(), bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("X-Signoz-Cloud-Api-Key", key)
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
for k, vs := range extraHeaders {
|
||||
for _, v := range vs {
|
||||
request.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
response, err := provider.httpClient.Do(request)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
description: Prefer SigNoz UI and icons across frontend code
|
||||
globs: **/*.{ts,tsx,js,jsx}
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# UI Components and Icons Source of Truth
|
||||
|
||||
For all frontend implementation work in this repository:
|
||||
|
||||
- Always use UI primitives/components from `@signozhq/ui`.
|
||||
- Always use icons from `@signozhq/icons`.
|
||||
- Do not introduce new usage of icon libraries directly (for example `lucide-react`) in app code.
|
||||
- Do not mix multiple component systems for the same UI surface when an equivalent exists in `@signozhq/ui`.
|
||||
|
||||
## Migration guidance
|
||||
|
||||
- If touching a file that already uses non-`@signozhq/icons` icons, prefer migrating that file to `@signozhq/icons` as part of the same change when practical.
|
||||
- If a required component or icon is missing from SigNoz packages, call this out explicitly in the PR/summary before introducing alternatives.
|
||||
@@ -1,19 +1,13 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && pnpm run commitlint --edit $1
|
||||
cd frontend && yarn run commitlint --edit $1
|
||||
|
||||
branch="$(git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
if [ -n "$TERM" ] && [ "$TERM" != "dumb" ]; then
|
||||
color_red="$(tput setaf 1)"
|
||||
bold="$(tput bold)"
|
||||
reset="$(tput sgr0)"
|
||||
else
|
||||
color_red=""
|
||||
bold=""
|
||||
reset=""
|
||||
fi
|
||||
color_red="$(tput setaf 1)"
|
||||
bold="$(tput bold)"
|
||||
reset="$(tput sgr0)"
|
||||
|
||||
if [ "$branch" = "main" ]; then
|
||||
echo "${color_red}${bold}You can't commit directly to the main branch${reset}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && pnpm lint-staged
|
||||
cd frontend && yarn lint-staged
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
registry = 'https://registry.npmjs.org/'
|
||||
engine-strict=true
|
||||
|
||||
public-hoist-pattern[]=@commitlint*
|
||||
public-hoist-pattern[]=commitlint
|
||||
registry = 'https://registry.npmjs.org/'
|
||||
@@ -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
|
||||
@@ -289,8 +291,6 @@
|
||||
// Prevents window.open(path), window.location.origin + path, window.location.href = path
|
||||
"signoz/no-antd-components": "error",
|
||||
// Prevents the usage of specific antd components in favor of our lib
|
||||
"signoz/no-signozhq-ui-barrel": "error",
|
||||
// Forces subpath imports (@signozhq/ui/<component>) instead of the eagerly-loaded barrel
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
@@ -411,7 +411,7 @@
|
||||
"jsx-a11y/media-has-caption": "warn",
|
||||
"jsx-a11y/mouse-events-have-key-events": "warn",
|
||||
"jsx-a11y/no-access-key": "error",
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"jsx-a11y/no-autofocus": "warn",
|
||||
"jsx-a11y/no-distracting-elements": "error",
|
||||
"jsx-a11y/no-redundant-roles": "warn",
|
||||
"jsx-a11y/role-has-required-aria-props": "warn",
|
||||
@@ -495,8 +495,7 @@
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"src/api/generated/**/*.ts",
|
||||
"src/api/ai-assistant/**/*.ts"
|
||||
"src/api/generated/**/*.ts"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
@@ -536,10 +535,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
|
||||
@@ -28,8 +28,8 @@ Follow the steps below
|
||||
1. ```git clone https://github.com/SigNoz/signoz.git && cd signoz/frontend```
|
||||
1. change baseURL to ```<test environment URL>``` in file ```src/constants/env.ts```
|
||||
|
||||
1. ```pnpm install```
|
||||
1. ```pnpm dev```
|
||||
1. ```yarn install```
|
||||
1. ```yarn dev```
|
||||
|
||||
```Note: Please ping us in #contributing channel in our slack community and we will DM you with <test environment URL>```
|
||||
|
||||
@@ -41,7 +41,7 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `pnpm start`
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3301](http://localhost:3301) to view it in the browser.
|
||||
@@ -49,12 +49,12 @@ Open [http://localhost:3301](http://localhost:3301) to view it in the browser.
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `pnpm test`
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `pnpm build`
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
@@ -64,7 +64,7 @@ Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `pnpm eject`
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
@@ -100,6 +100,6 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/a
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `pnpm build` fails to minify
|
||||
### `yarn build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const IconMock = React.forwardRef<SVGSVGElement, React.SVGProps<SVGSVGElement>>(
|
||||
(props, ref) => <svg ref={ref} {...props} />,
|
||||
);
|
||||
IconMock.displayName = 'IconMock';
|
||||
|
||||
// Returns a Proxy that resolves any named export to IconMock by default,
|
||||
// so `import { AnyIcon } from '@signozhq/icons'` always returns a valid component.
|
||||
// Pass `overrides` to swap in test-specific stubs (e.g. icons with data-testid).
|
||||
export function createIconsMock(
|
||||
overrides: Record<string, unknown> = {},
|
||||
): Record<string | symbol, unknown> {
|
||||
return new Proxy(
|
||||
{ __esModule: true, default: IconMock, ...overrides } as Record<
|
||||
string | symbol,
|
||||
unknown
|
||||
>,
|
||||
{
|
||||
get(target, prop: string | symbol): unknown {
|
||||
if (prop in target) {
|
||||
return target[prop as string];
|
||||
}
|
||||
return IconMock;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { createIconsMock } from './createIconsMock';
|
||||
|
||||
module.exports = createIconsMock();
|
||||
28
frontend/__mocks__/useSafeNavigate.ts
Normal file
28
frontend/__mocks__/useSafeNavigate.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// Mock for useSafeNavigate hook to avoid React Router version conflicts in tests
|
||||
interface SafeNavigateOptions {
|
||||
replace?: boolean;
|
||||
state?: unknown;
|
||||
newTab?: boolean;
|
||||
}
|
||||
|
||||
interface SafeNavigateTo {
|
||||
pathname?: string;
|
||||
search?: string;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
type SafeNavigateToType = string | SafeNavigateTo;
|
||||
|
||||
interface UseSafeNavigateReturn {
|
||||
safeNavigate: jest.MockedFunction<
|
||||
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||
>;
|
||||
}
|
||||
|
||||
export const useSafeNavigate = (): UseSafeNavigateReturn => ({
|
||||
safeNavigate: jest.fn(
|
||||
(_to: SafeNavigateToType, _options?: SafeNavigateOptions) => {},
|
||||
) as jest.MockedFunction<
|
||||
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||
>,
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { Config } from '@jest/types';
|
||||
|
||||
const USE_SAFE_NAVIGATE_MOCK_PATH =
|
||||
'<rootDir>/src/__tests__/safeNavigateMock.ts';
|
||||
const LOG_EVENT_MOCK_PATH = '<rootDir>/src/__tests__/logEventMock.ts';
|
||||
const USE_SAFE_NAVIGATE_MOCK_PATH = '<rootDir>/__mocks__/useSafeNavigate.ts';
|
||||
|
||||
const config: Config.InitialOptions = {
|
||||
silent: true,
|
||||
@@ -24,15 +22,13 @@ const config: Config.InitialOptions = {
|
||||
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^api/common/logEvent$': LOG_EVENT_MOCK_PATH,
|
||||
'^src/api/common/logEvent$': LOG_EVENT_MOCK_PATH,
|
||||
'^constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^src/constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^@signozhq/icons$': '<rootDir>/__mocks__/signozhqIconsMock.tsx',
|
||||
'^test-mocks/(.*)$': '<rootDir>/__mocks__/$1',
|
||||
'^@signozhq/icons$':
|
||||
'<rootDir>/node_modules/@signozhq/icons/dist/index.esm.js',
|
||||
'^react-syntax-highlighter/dist/esm/(.*)$':
|
||||
'<rootDir>/node_modules/react-syntax-highlighter/dist/cjs/$1',
|
||||
'^@signozhq/(?!ui(?:/|$))([^/]+)$':
|
||||
'^@signozhq/(?!ui$)([^/]+)$':
|
||||
'<rootDir>/node_modules/@signozhq/$1/dist/$1.js',
|
||||
},
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
@@ -50,11 +46,7 @@ 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)/)',
|
||||
// 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/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@chenglou/pretext|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* Adds custom matchers from the react testing library to all tests
|
||||
*/
|
||||
import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import 'jest-styled-components';
|
||||
|
||||
import { server } from './src/mocks-server/server';
|
||||
@@ -29,27 +28,6 @@ if (!HTMLElement.prototype.scrollIntoView) {
|
||||
HTMLElement.prototype.scrollIntoView = function (): void {};
|
||||
}
|
||||
|
||||
if (typeof window.IntersectionObserver === 'undefined') {
|
||||
class IntersectionObserverMock {
|
||||
observe(): void {}
|
||||
unobserve(): void {}
|
||||
disconnect(): void {}
|
||||
takeRecords(): IntersectionObserverEntry[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
(window as any).IntersectionObserver = IntersectionObserverMock;
|
||||
}
|
||||
|
||||
if (typeof window.ResizeObserver === 'undefined') {
|
||||
class ResizeObserverMock {
|
||||
observe(): void {}
|
||||
unobserve(): void {}
|
||||
disconnect(): void {}
|
||||
}
|
||||
(window as any).ResizeObserver = ResizeObserverMock;
|
||||
}
|
||||
|
||||
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
|
||||
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
|
||||
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"project": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"ignore": ["src/api/generated/**/*.ts", "src/typings/*.ts"],
|
||||
"ignoreDependencies": [
|
||||
"http-proxy-middleware",
|
||||
"@typescript/native-preview"
|
||||
]
|
||||
"ignore": ["src/api/generated/**/*.ts", "src/typings/*.ts"]
|
||||
}
|
||||
@@ -10,13 +10,6 @@ export default defineConfig({
|
||||
signoz: {
|
||||
input: {
|
||||
target: '../docs/api/openapi.yml',
|
||||
// Perses' `common.JSONRef` (used by `DashboardGridItem.content`) has a
|
||||
// field tagged `json:"$ref"`, so our spec contains a property literally
|
||||
// named `$ref`.
|
||||
// Orval v8's validator (`@scalar/openapi-parser`) treats every `$ref` key
|
||||
// as a JSON Reference and aborts with `INVALID_REFERENCE` when the value isn't a URI string.
|
||||
// Safe to disable: yes, the spec is generated by `cmd/openapi.go` and gated by backend CI, not hand-edited.
|
||||
unsafeDisableValidation: true,
|
||||
},
|
||||
output: {
|
||||
target: './src/api/generated/services',
|
||||
@@ -34,7 +27,7 @@ export default defineConfig({
|
||||
signal: true,
|
||||
useOperationIdAsQueryKey: false,
|
||||
},
|
||||
useDates: false,
|
||||
useDates: true,
|
||||
useNamedParameters: true,
|
||||
enumGenerationType: 'enum',
|
||||
mutator: {
|
||||
@@ -87,7 +80,7 @@ export default defineConfig({
|
||||
header: (info: { title: string; version: string }): string[] => [
|
||||
`! Do not edit manually`,
|
||||
`* The file has been auto-generated using Orval for SigNoz`,
|
||||
`* regenerate with 'pnpm generate:api'`,
|
||||
`* regenerate with 'yarn generate:api'`,
|
||||
...(info.title ? [info.title] : []),
|
||||
...(info.version ? [`OpenAPI spec version: ${info.version}`] : []),
|
||||
],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"i18n:generate-hash": "node ./i18-generate-hash.cjs",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@@ -19,43 +18,45 @@
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
"postinstall": "pnpm i18n:generate-hash && (is-ci || pnpm husky:configure) && node scripts/update-registry.cjs",
|
||||
"postinstall": "yarn i18n:generate-hash && (is-ci || yarn husky:configure) && node scripts/update-registry.cjs",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
|
||||
"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",
|
||||
"pnpm": ">=10.0.0 <11.0.0"
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "6.0.0",
|
||||
"@ant-design/icons": "4.8.0",
|
||||
"@codemirror/autocomplete": "6.18.6",
|
||||
"@codemirror/lang-javascript": "6.2.3",
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/view": "6.36.6",
|
||||
"@dnd-kit/core": "6.1.0",
|
||||
"@dnd-kit/modifiers": "7.0.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@dnd-kit/utilities": "3.2.2",
|
||||
"@grafana/data": "^11.6.14",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@grafana/data": "^11.2.3",
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@radix-ui/react-tabs": "1.0.4",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "8.41.0",
|
||||
"@sentry/vite-plugin": "2.22.6",
|
||||
"@signozhq/design-tokens": "2.1.4",
|
||||
"@signozhq/icons": "0.4.0",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/resizable": "0.0.2",
|
||||
"@signozhq/ui": "0.0.23",
|
||||
"@signozhq/ui": "0.0.17",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
"@uiw/codemirror-theme-github": "4.24.1",
|
||||
"@uiw/react-codemirror": "4.23.10",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
"@visx/group": "3.3.0",
|
||||
"@visx/hierarchy": "3.12.0",
|
||||
"@visx/shape": "3.5.0",
|
||||
@@ -65,36 +66,46 @@
|
||||
"antd": "5.11.0",
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
"antlr4": "4.13.2",
|
||||
"axios": "1.16.0",
|
||||
"axios": "1.12.0",
|
||||
"babel-jest": "^29.6.4",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-named-asset-import": "^0.3.7",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"babel-preset-react-app": "^10.0.1",
|
||||
"chart.js": "3.9.1",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"chartjs-plugin-annotation": "^1.4.0",
|
||||
"classnames": "2.3.2",
|
||||
"color": "^4.2.1",
|
||||
"color-alpha": "2.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"crypto-js": "4.2.0",
|
||||
"d3-hierarchy": "3.1.2",
|
||||
"dayjs": "^1.10.7",
|
||||
"dompurify": "3.4.0",
|
||||
"dompurify": "3.2.4",
|
||||
"dotenv": "8.2.0",
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"eventemitter3": "5.0.1",
|
||||
"fontfaceobserver": "2.3.0",
|
||||
"history": "4.10.1",
|
||||
"http-proxy-middleware": "4.0.0",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"http-status-codes": "2.3.0",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^4.0.0",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
"immer": "11.1.3",
|
||||
"jest": "30.2.0",
|
||||
"js-base64": "^3.7.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "0.498.0",
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"motion": "12.4.13",
|
||||
"nuqs": "2.8.8",
|
||||
"overlayscrollbars": "^2.8.1",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"papaparse": "5.4.1",
|
||||
"posthog-js": "1.298.0",
|
||||
"rc-select": "14.10.0",
|
||||
"rc-tween-one": "3.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-addons-update": "15.6.3",
|
||||
"react-beautiful-dnd": "13.1.1",
|
||||
@@ -102,6 +113,7 @@
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-drag-listview": "2.0.0",
|
||||
"react-error-boundary": "4.0.11",
|
||||
"react-force-graph-2d": "^1.29.1",
|
||||
"react-full-screen": "1.1.1",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
@@ -109,28 +121,31 @@
|
||||
"react-hook-form": "7.71.2",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-json-tree": "^0.20.0",
|
||||
"react-lottie": "1.2.10",
|
||||
"react-markdown": "8.0.7",
|
||||
"react-query": "3.39.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-rnd": "^10.5.3",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom-v5-compat": "6.30.3",
|
||||
"react-router-dom-v5-compat": "6.27.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"react-use": "^17.3.2",
|
||||
"react-virtuoso": "4.0.3",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rehype-raw": "7.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rollup-plugin-visualizer": "7.0.0",
|
||||
"rrule": "2.8.1",
|
||||
"stream": "^0.0.2",
|
||||
"styled-components": "^5.3.11",
|
||||
"timestamp-nano": "^1.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "5.9.3",
|
||||
"uplot": "1.6.31",
|
||||
"uuid": "^8.3.2",
|
||||
"vite": "npm:rolldown-vite@7.3.1",
|
||||
"vite-plugin-html": "3.2.2",
|
||||
"web-vitals": "^0.2.4",
|
||||
"zod": "4.3.6",
|
||||
"zustand": "5.0.11"
|
||||
},
|
||||
@@ -149,23 +164,25 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.11",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-jsx": "^7.12.13",
|
||||
"@babel/preset-env": "^7.22.14",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/cli": "20.4.4",
|
||||
"@commitlint/config-conventional": "20.4.4",
|
||||
"@jest/globals": "30.4.1",
|
||||
"@jest/types": "30.2.0",
|
||||
"@commitlint/cli": "^20.4.2",
|
||||
"@commitlint/config-conventional": "^20.4.2",
|
||||
"@faker-js/faker": "9.3.0",
|
||||
"@jest/globals": "30.2.0",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@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/dompurify": "^2.4.0",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/fontfaceobserver": "2.1.0",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/papaparse": "5.3.7",
|
||||
"@types/react": "18.0.26",
|
||||
@@ -173,37 +190,48 @@
|
||||
"@types/react-beautiful-dnd": "13.1.8",
|
||||
"@types/react-dom": "18.0.10",
|
||||
"@types/react-grid-layout": "^1.1.2",
|
||||
"@types/react-helmet-async": "1.0.3",
|
||||
"@types/react-lottie": "1.2.10",
|
||||
"@types/react-redux": "^7.1.11",
|
||||
"@types/react-resizable": "3.0.3",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/react-syntax-highlighter": "15.5.13",
|
||||
"@types/redux-mock-store": "1.0.4",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260430.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260421.2",
|
||||
"autoprefixer": "10.4.19",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"eslint-plugin-sonarjs": "4.0.2",
|
||||
"glob": "^13.0.6",
|
||||
"husky": "^7.0.4",
|
||||
"imagemin": "^8.0.1",
|
||||
"imagemin-svgo": "^10.0.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-environment-node": "29.7.0",
|
||||
"jest-styled-components": "^7.2.0",
|
||||
"json-schema-to-typescript": "^15.0.4",
|
||||
"lint-staged": "^17.0.4",
|
||||
"lint-staged": "^12.5.0",
|
||||
"msw": "1.3.2",
|
||||
"orval": "8.9.1",
|
||||
"npm-run-all": "latest",
|
||||
"orval": "7.18.0",
|
||||
"oxfmt": "0.47.0",
|
||||
"oxlint": "1.62.0",
|
||||
"oxlint-tsgolint": "0.22.1",
|
||||
"postcss": "8.5.14",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-scss": "4.0.9",
|
||||
"prop-types": "15.8.1",
|
||||
"react-hooks-testing-library": "0.6.0",
|
||||
"react-resizable": "3.0.4",
|
||||
"redux-mock-store": "1.5.4",
|
||||
"sass": "1.97.3",
|
||||
"sharp": "0.34.5",
|
||||
"stylelint": "17.7.0",
|
||||
"svgo": "4.0.1",
|
||||
"ts-jest": "29.4.9",
|
||||
"stylelint-scss": "7.0.0",
|
||||
"svgo": "4.0.0",
|
||||
"ts-api-utils": "2.4.0",
|
||||
"ts-jest": "29.4.6",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "5.2.0",
|
||||
"use-sync-external-store": "1.6.0",
|
||||
"vite-plugin-checker": "0.12.0",
|
||||
@@ -231,7 +259,7 @@
|
||||
"xml2js": "0.5.0",
|
||||
"phin": "^3.7.1",
|
||||
"body-parser": "1.20.3",
|
||||
"http-proxy-middleware": "4.0.0",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"cross-spawn": "7.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"serialize-javascript": "6.0.2",
|
||||
@@ -243,4 +271,4 @@
|
||||
"tmp": "0.2.4",
|
||||
"vite": "npm:rolldown-vite@7.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,7 @@
|
||||
*/
|
||||
|
||||
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.',
|
||||
Typography: 'Use @signozhq/ui Typography instead of antd Typography.',
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
/**
|
||||
* Rule: no-signozhq-ui-barrel
|
||||
*
|
||||
* Forbids importing from the `@signozhq/ui` barrel and requires the matching
|
||||
* subpath instead.
|
||||
*
|
||||
* This rule catches:
|
||||
* import { Typography } from '@signozhq/ui'
|
||||
* import { Button, toast } from '@signozhq/ui'
|
||||
* import '@signozhq/ui'
|
||||
*
|
||||
* And expects:
|
||||
* import { Typography } from '@signozhq/ui/typography'
|
||||
* import { Button } from '@signozhq/ui/button'
|
||||
* import { toast } from '@signozhq/ui/sonner'
|
||||
*
|
||||
* Why: the barrel eagerly require()s every component (~90 of them) along with
|
||||
* their Radix/cmdk/motion/react-day-picker dependencies. Under Jest this caused
|
||||
* 5s timeouts and flaky tests after the Antd→@signozhq/ui Typography migration
|
||||
* (#11199). Subpath imports (added in @signozhq/ui@0.0.18) load only what's
|
||||
* used.
|
||||
*
|
||||
* The auto-generated `auto-import-registry.d.ts` is a pure declaration file
|
||||
* that exists solely to nudge VS Code's auto-import indexer; its bare
|
||||
* `import '@signozhq/ui';` is type-only and not emitted, so it is exempt.
|
||||
*
|
||||
* Autofix:
|
||||
* Rewrites named imports to the matching subpath, splitting one statement
|
||||
* into multiple when specifiers come from different subpaths. The
|
||||
* export-name → subpath map is derived lazily from the installed
|
||||
* `@signozhq/ui` dist `.d.ts` files. Imports we can't classify (namespace,
|
||||
* default, side-effect, or unknown specifier) are reported without a fix.
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ALLOWED_FILES = new Set(['auto-import-registry.d.ts']);
|
||||
|
||||
const PLUGIN_DIR = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
let exportMap = null;
|
||||
|
||||
function loadExportMap() {
|
||||
if (exportMap === null) {
|
||||
exportMap = buildExportMap();
|
||||
}
|
||||
return exportMap;
|
||||
}
|
||||
|
||||
function buildExportMap() {
|
||||
const map = new Map();
|
||||
const root = findSignozUiRoot();
|
||||
if (!root) return map;
|
||||
|
||||
let pkg;
|
||||
try {
|
||||
pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
|
||||
} catch {
|
||||
return map;
|
||||
}
|
||||
|
||||
const subpathKeys = Object.keys(pkg.exports || {}).filter((k) => k !== '.');
|
||||
for (const key of subpathKeys) {
|
||||
const subpath = key.replace(/^\.\//, '');
|
||||
const entry = join(root, 'dist', subpath, 'index.d.ts');
|
||||
if (!existsSync(entry)) continue;
|
||||
|
||||
const names = new Set();
|
||||
collectExportedNames(entry, names, new Set());
|
||||
// First-wins: package.json subpath order is the canonical home for
|
||||
// names re-exported across multiple subpaths (e.g. `ToggleColor` is
|
||||
// declared in `toggle` and re-exported from `toggle-group`).
|
||||
for (const name of names) {
|
||||
if (!map.has(name)) map.set(name, subpath);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function findSignozUiRoot() {
|
||||
let dir = PLUGIN_DIR;
|
||||
while (true) {
|
||||
const candidate = join(dir, 'node_modules', '@signozhq', 'ui');
|
||||
if (existsSync(join(candidate, 'package.json'))) return candidate;
|
||||
const parent = dirname(dir);
|
||||
if (parent === dir) return null;
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
function collectExportedNames(filepath, out, visited) {
|
||||
if (visited.has(filepath) || !existsSync(filepath)) return;
|
||||
visited.add(filepath);
|
||||
|
||||
let content;
|
||||
try {
|
||||
content = readFileSync(filepath, 'utf-8');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// `export * from './x.js'` / `export type * from './x.js'`
|
||||
for (const m of content.matchAll(
|
||||
/export\s+(?:type\s+)?\*\s+from\s+['"]([^'"]+)['"]/g,
|
||||
)) {
|
||||
collectExportedNames(resolveRelativeDts(filepath, m[1]), out, visited);
|
||||
}
|
||||
|
||||
// `export { Foo, type Bar, Foo as Baz } from '...';` and `export { ... };`
|
||||
for (const m of content.matchAll(/export\s+(?:type\s+)?\{([^}]*)\}/g)) {
|
||||
for (const item of m[1].split(',')) {
|
||||
const cleaned = item.trim().replace(/^type\s+/, '');
|
||||
if (!cleaned) continue;
|
||||
const idMatch = cleaned.match(
|
||||
/^([A-Za-z_$][A-Za-z0-9_$]*)(?:\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*))?$/,
|
||||
);
|
||||
if (idMatch) out.add(idMatch[2] || idMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// `export (declare) const|let|var|function|class|enum|type|interface Foo`
|
||||
for (const m of content.matchAll(
|
||||
/export\s+(?:declare\s+)?(?:const|let|var|function|class|enum|type|interface)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g,
|
||||
)) {
|
||||
out.add(m[1]);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRelativeDts(fromFile, spec) {
|
||||
const base = dirname(fromFile);
|
||||
const stripped = spec.replace(/\.(js|mjs|cjs)$/, '');
|
||||
const sibling = join(base, `${stripped}.d.ts`);
|
||||
if (existsSync(sibling)) return sibling;
|
||||
const indexed = join(base, stripped, 'index.d.ts');
|
||||
if (existsSync(indexed)) return indexed;
|
||||
return sibling;
|
||||
}
|
||||
|
||||
function buildReplacement(node, map) {
|
||||
const specifiers = node.specifiers || [];
|
||||
if (specifiers.length === 0) return null;
|
||||
|
||||
for (const spec of specifiers) {
|
||||
if (spec.type !== 'ImportSpecifier') return null;
|
||||
if (spec.imported?.type !== 'Identifier') return null;
|
||||
}
|
||||
|
||||
const quote = node.source.raw?.[0] === '"' ? '"' : "'";
|
||||
const topLevelType = node.importKind === 'type';
|
||||
const keyword = topLevelType ? 'import type' : 'import';
|
||||
|
||||
const groups = new Map();
|
||||
for (const spec of specifiers) {
|
||||
const importedName = spec.imported.name;
|
||||
const subpath = map.get(importedName);
|
||||
if (!subpath) return null;
|
||||
|
||||
const localName = spec.local.name;
|
||||
const inlineType = !topLevelType && spec.importKind === 'type';
|
||||
let text = inlineType ? 'type ' : '';
|
||||
text += importedName;
|
||||
if (localName !== importedName) text += ` as ${localName}`;
|
||||
|
||||
if (!groups.has(subpath)) groups.set(subpath, []);
|
||||
groups.get(subpath).push(text);
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
for (const [subpath, items] of groups) {
|
||||
lines.push(
|
||||
`${keyword} { ${items.join(', ')} } from ${quote}@signozhq/ui/${subpath}${quote};`,
|
||||
);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
fixable: 'code',
|
||||
},
|
||||
create(context) {
|
||||
const filename = context.filename || '';
|
||||
const basename = filename.split(/[\\/]/).pop();
|
||||
if (ALLOWED_FILES.has(basename)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
if (node.source.value !== '@signozhq/ui') {
|
||||
return;
|
||||
}
|
||||
|
||||
const replacement = buildReplacement(node, loadExportMap());
|
||||
const report = {
|
||||
node: node.source,
|
||||
message:
|
||||
"Do not import from the '@signozhq/ui' barrel. Use the matching subpath instead (e.g. '@signozhq/ui/typography', '@signozhq/ui/button', '@signozhq/ui/sonner'). The barrel eagerly loads ~90 components and slows tests substantially.",
|
||||
};
|
||||
if (replacement) {
|
||||
report.fix = (fixer) => fixer.replaceText(node, replacement);
|
||||
}
|
||||
context.report(report);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -10,7 +10,6 @@ import noNavigatorClipboard from './rules/no-navigator-clipboard.mjs';
|
||||
import noUnsupportedAssetPattern from './rules/no-unsupported-asset-pattern.mjs';
|
||||
import noRawAbsolutePath from './rules/no-raw-absolute-path.mjs';
|
||||
import noAntdComponents from './rules/no-antd-components.mjs';
|
||||
import noSignozhqUiBarrel from './rules/no-signozhq-ui-barrel.mjs';
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
@@ -22,6 +21,5 @@ export default {
|
||||
'no-unsupported-asset-pattern': noUnsupportedAssetPattern,
|
||||
'no-raw-absolute-path': noRawAbsolutePath,
|
||||
'no-antd-components': noAntdComponents,
|
||||
'no-signozhq-ui-barrel': noSignozhqUiBarrel,
|
||||
},
|
||||
};
|
||||
|
||||
18754
frontend/pnpm-lock.yaml
generated
18754
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
trustPolicy: no-downgrade
|
||||
minimumReleaseAge: 2880 # 2d
|
||||
minimumReleaseAgeStrict: true
|
||||
minimumReleaseAgeExclude:
|
||||
- '@signozhq/*'
|
||||
blockExoticSubdeps: true
|
||||
@@ -26,6 +26,5 @@
|
||||
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
|
||||
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
|
||||
"your_graph_build_with": "Your graph built with",
|
||||
"dashboard_ok_confirm": "query will be saved. Press OK to confirm.",
|
||||
"variable_name_already_exists": "Variable \"{{name}}\" already exists"
|
||||
"dashboard_ok_confirm": "query will be saved. Press OK to confirm."
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -30,6 +30,5 @@
|
||||
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
|
||||
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
|
||||
"your_graph_build_with": "Your graph built with",
|
||||
"dashboard_ok_confirm": "query will be saved. Press OK to confirm.",
|
||||
"variable_name_already_exists": "Variable \"{{name}}\" already exists"
|
||||
"dashboard_ok_confirm": "query will be saved. Press OK to confirm."
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ echo "\n✅ Tag files renamed to index.ts"
|
||||
|
||||
# Format generated files
|
||||
echo "\n\n---\nRunning prettier...\n"
|
||||
if ! pnpm prettify src/api/generated; then
|
||||
if ! yarn prettify src/api/generated; then
|
||||
echo "Formatting failed!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -25,7 +25,7 @@ echo "\n✅ Formatting successful"
|
||||
|
||||
# Fix linting issues
|
||||
echo "\n\n---\nRunning lint...\n"
|
||||
if ! pnpm lint:generated; then
|
||||
if ! yarn lint:generated; then
|
||||
echo "Lint check failed! Please fix linting errors before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -17,12 +17,6 @@ registered_languages=$(grep -oP "registerLanguage\('\K[^']+" "$SYNTAX_HIGHLIGHTE
|
||||
missing_languages=()
|
||||
|
||||
for lang in $md_languages; do
|
||||
# Skip ai-* block markers — these are custom AI block types rendered by
|
||||
# RichCodeBlock as React components (e.g. ActionBlock, LineChartBlock),
|
||||
# not real syntax languages, so they don't need highlighter registration.
|
||||
if [[ "$lang" == ai-* ]]; then
|
||||
continue
|
||||
fi
|
||||
if ! echo "$registered_languages" | grep -qx "$lang"; then
|
||||
missing_languages+=("$lang")
|
||||
fi
|
||||
|
||||
@@ -8,7 +8,6 @@ import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { ORG_PREFERENCES } from 'constants/orgPreferences';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
||||
@@ -41,8 +40,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
} = useAppContext();
|
||||
|
||||
const isAdmin = user.role === USER_ROLES.ADMIN;
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
|
||||
const mapRoutes = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
@@ -102,14 +99,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
if (
|
||||
(pathname === ROUTES.AI_ASSISTANT_BASE ||
|
||||
pathname.startsWith('/ai-assistant/')) &&
|
||||
!isAIAssistantEnabled
|
||||
) {
|
||||
return <Redirect to={ROUTES.HOME} />;
|
||||
}
|
||||
|
||||
// Check for workspace access restriction (cloud only)
|
||||
const isCloudPlatform = activeLicense?.platform === LicensePlatform.CLOUD;
|
||||
|
||||
|
||||
@@ -164,18 +164,14 @@ function createMockAppContext(
|
||||
featureFlags: [],
|
||||
orgPreferences: createMockOrgPreferences(),
|
||||
userPreferences: [],
|
||||
hostsData: null,
|
||||
isLoggedIn: true,
|
||||
isPreflightLoading: false,
|
||||
org: [{ createdAt: 0, id: 'org-id', displayName: 'Test Org' }],
|
||||
isFetchingUser: false,
|
||||
isFetchingActiveLicense: false,
|
||||
isFetchingHosts: false,
|
||||
isFetchingFeatureFlags: false,
|
||||
isFetchingOrgPreferences: false,
|
||||
userFetchError: null,
|
||||
activeLicenseFetchError: null,
|
||||
hostsFetchError: null,
|
||||
featureFlagsFetchError: null,
|
||||
orgPreferencesFetchError: null,
|
||||
changelog: null,
|
||||
|
||||
@@ -18,7 +18,6 @@ import AppLayout from 'container/AppLayout';
|
||||
import Hex from 'crypto-js/enc-hex';
|
||||
import HmacSHA256 from 'crypto-js/hmac-sha256';
|
||||
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { useIsDarkMode, useThemeConfig } from 'hooks/useDarkMode';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { NotificationProvider } from 'hooks/useNotifications';
|
||||
@@ -59,24 +58,15 @@ function App(): JSX.Element {
|
||||
isLoggedIn: isLoggedInState,
|
||||
featureFlags,
|
||||
org,
|
||||
isPreflightLoading,
|
||||
} = useAppContext();
|
||||
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
|
||||
const { hostname } = window.location;
|
||||
const [pathname, setPathname] = useState(history.location.pathname);
|
||||
const { hostname, pathname } = window.location;
|
||||
|
||||
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
|
||||
|
||||
const [isSentryInitialized, setIsSentryInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
return history.listen((location) => {
|
||||
setPathname(location.pathname);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const enableAnalytics = useCallback(
|
||||
(user: IUser): void => {
|
||||
// wait for the required data to be loaded before doing init for anything!
|
||||
@@ -222,27 +212,6 @@ function App(): JSX.Element {
|
||||
activeLicenseFetchError,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedInState) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRoutes((prev) => {
|
||||
const hasAi = prev.some((r) => r.key === 'AI_ASSISTANT');
|
||||
if (isAIAssistantEnabled === hasAi) {
|
||||
return prev;
|
||||
}
|
||||
if (isAIAssistantEnabled) {
|
||||
const aiRoute = defaultRoutes.find((r) => r.key === 'AI_ASSISTANT');
|
||||
if (!aiRoute) {
|
||||
return prev;
|
||||
}
|
||||
return [...prev.filter((r) => r.key !== 'AI_ASSISTANT'), aiRoute];
|
||||
}
|
||||
return prev.filter((r) => r.key !== 'AI_ASSISTANT');
|
||||
});
|
||||
}, [isLoggedInState, isAIAssistantEnabled]);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -252,9 +221,7 @@ function App(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (
|
||||
pathname === ROUTES.ONBOARDING ||
|
||||
pathname.startsWith('/public/dashboard/') ||
|
||||
pathname === '/ai-assistant' ||
|
||||
pathname.startsWith('/ai-assistant/')
|
||||
pathname.startsWith('/public/dashboard/')
|
||||
) {
|
||||
window.Pylon?.('hideChatBubble');
|
||||
} else {
|
||||
@@ -291,8 +258,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 +300,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,
|
||||
@@ -367,11 +327,6 @@ function App(): JSX.Element {
|
||||
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||
beforeSend(event) {
|
||||
// Drop the event if its level is 'warning' or 'info'
|
||||
if (event.level === 'warning' || event.level === 'info') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessionReplayUrl = posthog.get_session_replay_url?.({
|
||||
withTimestamp: true,
|
||||
});
|
||||
@@ -395,10 +350,6 @@ function App(): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isCloudUser, isEnterpriseSelfHostedUser]);
|
||||
|
||||
if (isPreflightLoading) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
// if the user is in logged in state
|
||||
if (isLoggedInState) {
|
||||
// if the setup calls are loading then return a spinner
|
||||
|
||||
@@ -47,6 +47,7 @@ export const TracesFunnels = Loadable(
|
||||
import(/* webpackChunkName: "Traces Funnels" */ 'pages/TracesModulePage'),
|
||||
);
|
||||
export const TracesFunnelDetails = Loadable(
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "Traces Funnel Details" */ 'pages/TracesModulePage'
|
||||
@@ -312,14 +313,14 @@ export const PublicDashboardPage = Loadable(
|
||||
),
|
||||
);
|
||||
|
||||
export const AlertTypeSelectionPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "Alert Type Selection Page" */ 'pages/AlertTypeSelection'
|
||||
),
|
||||
);
|
||||
|
||||
export const MeterExplorerPage = Loadable(
|
||||
() =>
|
||||
import(/* webpackChunkName: "Meter Explorer Page" */ 'pages/MeterExplorer'),
|
||||
);
|
||||
|
||||
export const AIAssistantPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "AI Assistant Page" */ 'pages/AIAssistantPage/AIAssistantPage'
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@ import { RouteProps } from 'react-router-dom';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import {
|
||||
AIAssistantPage,
|
||||
AlertHistory,
|
||||
AlertOverview,
|
||||
AlertTypeSelectionPage,
|
||||
AllAlertChannels,
|
||||
AllErrors,
|
||||
ApiMonitoring,
|
||||
@@ -143,18 +143,18 @@ const routes: AppRoutes[] = [
|
||||
// /trace-old serves V3 (URL-only access). Flip the two `component`
|
||||
// values back to release V3.
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL_OLD,
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
component: TraceDetail,
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL_OLD',
|
||||
key: 'TRACE_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
path: ROUTES.TRACE_DETAIL_OLD,
|
||||
exact: true,
|
||||
component: TraceDetailV3,
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL',
|
||||
key: 'TRACE_DETAIL_OLD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
@@ -212,6 +212,13 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'LIST_ALL_ALERT',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALERT_TYPE_SELECTION,
|
||||
exact: true,
|
||||
component: AlertTypeSelectionPage,
|
||||
isPrivate: true,
|
||||
key: 'ALERT_TYPE_SELECTION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALERTS_NEW,
|
||||
exact: true,
|
||||
@@ -500,13 +507,6 @@ const routes: AppRoutes[] = [
|
||||
key: 'API_MONITORING',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: [ROUTES.AI_ASSISTANT_BASE, ROUTES.AI_ASSISTANT],
|
||||
exact: true,
|
||||
component: AIAssistantPage,
|
||||
key: 'AI_ASSISTANT',
|
||||
isPrivate: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const SUPPORT_ROUTE: AppRoutes = {
|
||||
@@ -525,6 +525,18 @@ export const LIST_LICENSES: AppRoutes = {
|
||||
key: 'LIST_LICENSES',
|
||||
};
|
||||
|
||||
export const oldRoutes = [
|
||||
'/pipelines',
|
||||
'/logs-explorer',
|
||||
'/logs-explorer/live',
|
||||
'/logs-save-views',
|
||||
'/traces-save-views',
|
||||
'/settings/access-tokens',
|
||||
'/settings/api-keys',
|
||||
'/messaging-queues',
|
||||
'/alerts/edit',
|
||||
];
|
||||
|
||||
export const oldNewRoutesMapping: Record<string, string> = {
|
||||
'/pipelines': '/logs/pipelines',
|
||||
'/logs-explorer': '/logs/logs-explorer',
|
||||
@@ -535,9 +547,7 @@ export const oldNewRoutesMapping: Record<string, string> = {
|
||||
'/settings/api-keys': '/settings/service-accounts',
|
||||
'/messaging-queues': '/messaging-queues/overview',
|
||||
'/alerts/edit': '/alerts/overview',
|
||||
'/alerts/type-selection': '/alerts/new',
|
||||
};
|
||||
export const oldRoutes = Object.keys(oldNewRoutesMapping);
|
||||
|
||||
export const ROUTES_NOT_TO_BE_OVERRIDEN: string[] = [
|
||||
ROUTES.WORKSPACE_LOCKED,
|
||||
|
||||
@@ -21,10 +21,9 @@ i18n
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
},
|
||||
backend: {
|
||||
loadPath: (languages: string[], namespaces: string[]): string => {
|
||||
const language = languages[0];
|
||||
const namespace = namespaces[0];
|
||||
const pathkey = `/${language}/${namespace}`;
|
||||
loadPath: (language, namespace) => {
|
||||
const ns = namespace[0];
|
||||
const pathkey = `/${language}/${ns}`;
|
||||
const hash = cacheBursting[pathkey as keyof typeof cacheBursting] || '';
|
||||
return `${getBasePath()}locales/${language}/${namespace}.json?h=${hash}`;
|
||||
},
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Shared mock for `api/common/logEvent`.
|
||||
// Wired into jest.config.ts moduleNameMapper, so any import of
|
||||
// `api/common/logEvent` in test code resolves to this file.
|
||||
// Tests can import `logEventMock` to assert analytics calls — Jest's
|
||||
// `clearMocks: true` resets call history between tests.
|
||||
|
||||
export const logEventMock: jest.MockedFunction<
|
||||
(eventName: string, attributes?: Record<string, unknown>) => void
|
||||
> = jest.fn();
|
||||
|
||||
export default logEventMock;
|
||||
@@ -1,271 +0,0 @@
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest, RestRequest } from 'msw';
|
||||
import { MetricRangePayloadV5 } from 'types/api/v5/queryRange';
|
||||
|
||||
const QUERY_RANGE_URL = `${ENVIRONMENT.baseURL}/api/v5/query_range`;
|
||||
|
||||
export type MockLogsOptions = {
|
||||
offset?: number;
|
||||
pageSize?: number;
|
||||
hasMore?: boolean;
|
||||
delay?: number;
|
||||
onReceiveRequest?: (
|
||||
req: RestRequest,
|
||||
) =>
|
||||
| undefined
|
||||
| void
|
||||
| Omit<MockLogsOptions, 'onReceiveRequest'>
|
||||
| Promise<Omit<MockLogsOptions, 'onReceiveRequest'>>
|
||||
| Promise<void>;
|
||||
};
|
||||
|
||||
const createLogsResponse = ({
|
||||
offset = 0,
|
||||
pageSize = 100,
|
||||
hasMore = true,
|
||||
}: MockLogsOptions): MetricRangePayloadV5 => {
|
||||
const itemsForThisPage = hasMore ? pageSize : pageSize / 2;
|
||||
|
||||
return {
|
||||
data: {
|
||||
type: 'raw',
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
queryName: 'A',
|
||||
rows: Array.from({ length: itemsForThisPage }, (_, index) => {
|
||||
const cumulativeIndex = offset + index;
|
||||
const baseTimestamp = new Date('2024-02-15T21:20:22Z').getTime();
|
||||
const currentTimestamp = new Date(
|
||||
baseTimestamp - cumulativeIndex * 1000,
|
||||
);
|
||||
const timestampString = currentTimestamp.toISOString();
|
||||
const id = `log-id-${cumulativeIndex}`;
|
||||
const logLevel = ['INFO', 'WARN', 'ERROR'][cumulativeIndex % 3];
|
||||
const service = ['frontend', 'backend', 'database'][cumulativeIndex % 3];
|
||||
|
||||
return {
|
||||
timestamp: timestampString,
|
||||
data: {
|
||||
attributes_bool: {},
|
||||
attributes_float64: {},
|
||||
attributes_int64: {},
|
||||
attributes_string: {
|
||||
host_name: 'test-host',
|
||||
log_level: logLevel,
|
||||
service,
|
||||
},
|
||||
body: `${timestampString} ${logLevel} ${service} Log message ${cumulativeIndex}`,
|
||||
id,
|
||||
resources_string: {
|
||||
'host.name': 'test-host',
|
||||
},
|
||||
severity_number: [9, 13, 17][cumulativeIndex % 3],
|
||||
severity_text: logLevel,
|
||||
span_id: `span-${cumulativeIndex}`,
|
||||
trace_flags: 0,
|
||||
trace_id: `trace-${cumulativeIndex}`,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
bytesScanned: 0,
|
||||
durationMs: 0,
|
||||
rowsScanned: 0,
|
||||
stepIntervals: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export function mockQueryRangeV5WithLogsResponse({
|
||||
hasMore = true,
|
||||
offset = 0,
|
||||
pageSize = 100,
|
||||
delay = 0,
|
||||
onReceiveRequest,
|
||||
}: MockLogsOptions = {}): void {
|
||||
server.use(
|
||||
rest.post(QUERY_RANGE_URL, async (req, res, ctx) =>
|
||||
res(
|
||||
...(delay ? [ctx.delay(delay)] : []),
|
||||
ctx.status(200),
|
||||
ctx.json(
|
||||
createLogsResponse(
|
||||
(await onReceiveRequest?.(req)) ?? {
|
||||
hasMore,
|
||||
pageSize,
|
||||
offset,
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function mockQueryRangeV5WithError(
|
||||
error: string,
|
||||
statusCode = 500,
|
||||
): void {
|
||||
server.use(
|
||||
rest.post(QUERY_RANGE_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(statusCode),
|
||||
ctx.json({
|
||||
error,
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export type MockEventsOptions = {
|
||||
offset?: number;
|
||||
pageSize?: number;
|
||||
hasMore?: boolean;
|
||||
delay?: number;
|
||||
onReceiveRequest?: (
|
||||
req: RestRequest,
|
||||
) =>
|
||||
| undefined
|
||||
| void
|
||||
| Omit<MockEventsOptions, 'onReceiveRequest'>
|
||||
| Promise<Omit<MockEventsOptions, 'onReceiveRequest'>>
|
||||
| Promise<void>;
|
||||
};
|
||||
|
||||
const createEventsResponse = ({
|
||||
offset = 0,
|
||||
pageSize = 10,
|
||||
hasMore = true,
|
||||
}: MockEventsOptions): MetricRangePayloadV5 => {
|
||||
const itemsForThisPage = hasMore ? pageSize : Math.ceil(pageSize / 2);
|
||||
|
||||
const eventReasons = [
|
||||
'BackoffLimitExceeded',
|
||||
'SuccessfulCreate',
|
||||
'Pulled',
|
||||
'Created',
|
||||
'Started',
|
||||
'Killing',
|
||||
];
|
||||
const severityTexts = ['Warning', 'Normal'];
|
||||
const severityNumbers = [13, 9];
|
||||
const objectKinds = ['Job', 'Pod', 'Deployment', 'ReplicaSet'];
|
||||
const eventBodies = [
|
||||
'Job has reached the specified backoff limit',
|
||||
'Created pod: demo-pod',
|
||||
'Successfully pulled image',
|
||||
'Created container',
|
||||
'Started container',
|
||||
'Stopping container',
|
||||
];
|
||||
|
||||
return {
|
||||
data: {
|
||||
type: 'raw',
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
queryName: 'A',
|
||||
nextCursor: hasMore ? 'next-cursor-token' : '',
|
||||
rows: Array.from({ length: itemsForThisPage }, (_, index) => {
|
||||
const cumulativeIndex = offset + index;
|
||||
const baseTimestamp = new Date('2026-04-21T17:54:33Z').getTime();
|
||||
const currentTimestamp = new Date(
|
||||
baseTimestamp - cumulativeIndex * 60000,
|
||||
);
|
||||
const timestampString = currentTimestamp.toISOString();
|
||||
const id = `event-id-${cumulativeIndex}`;
|
||||
const severityIndex = cumulativeIndex % 2;
|
||||
const reasonIndex = cumulativeIndex % eventReasons.length;
|
||||
const kindIndex = cumulativeIndex % objectKinds.length;
|
||||
|
||||
return {
|
||||
timestamp: timestampString,
|
||||
data: {
|
||||
attributes_bool: {},
|
||||
attributes_number: {
|
||||
'k8s.event.count': 1,
|
||||
},
|
||||
attributes_string: {
|
||||
'k8s.event.action': '',
|
||||
'k8s.event.name': `demo-event-${cumulativeIndex}.${Math.random()
|
||||
.toString(36)
|
||||
.substring(7)}`,
|
||||
'k8s.event.reason': eventReasons[reasonIndex],
|
||||
'k8s.event.start_time': `${currentTimestamp.toISOString()} +0000 UTC`,
|
||||
'k8s.event.uid': `uid-${cumulativeIndex}`,
|
||||
'k8s.namespace.name': 'demo-apps',
|
||||
},
|
||||
body: eventBodies[reasonIndex],
|
||||
id,
|
||||
resources_string: {
|
||||
'k8s.cluster.name': 'signoz-test',
|
||||
'k8s.node.name': '',
|
||||
'k8s.object.api_version': 'batch/v1',
|
||||
'k8s.object.fieldpath': '',
|
||||
'k8s.object.kind': objectKinds[kindIndex],
|
||||
'k8s.object.name': `demo-object-${cumulativeIndex}`,
|
||||
'k8s.object.resource_version': `${462900 + cumulativeIndex}`,
|
||||
'k8s.object.uid': `object-uid-${cumulativeIndex}`,
|
||||
'signoz.component': 'otel-deployment',
|
||||
},
|
||||
scope_name:
|
||||
'github.com/open-telemetry/opentelemetry-collector-contrib/receiver/k8seventsreceiver',
|
||||
scope_string: {},
|
||||
scope_version: '0.139.0',
|
||||
severity_number: severityNumbers[severityIndex],
|
||||
severity_text: severityTexts[severityIndex],
|
||||
span_id: '',
|
||||
timestamp: currentTimestamp.getTime() * 1000000,
|
||||
trace_flags: 0,
|
||||
trace_id: '',
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
bytesScanned: 9682976,
|
||||
durationMs: 295,
|
||||
rowsScanned: 34198,
|
||||
stepIntervals: {
|
||||
A: 170,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export function mockQueryRangeV5WithEventsResponse({
|
||||
hasMore = true,
|
||||
offset = 0,
|
||||
pageSize = 10,
|
||||
delay = 0,
|
||||
onReceiveRequest,
|
||||
}: MockEventsOptions = {}): void {
|
||||
server.use(
|
||||
rest.post(QUERY_RANGE_URL, async (req, res, ctx) =>
|
||||
res(
|
||||
...(delay ? [ctx.delay(delay)] : []),
|
||||
ctx.status(200),
|
||||
ctx.json(
|
||||
createEventsResponse(
|
||||
(await onReceiveRequest?.(req)) ?? {
|
||||
hasMore,
|
||||
pageSize,
|
||||
offset,
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Shared mock for `hooks/useSafeNavigate`.
|
||||
// Wired into jest.config.ts moduleNameMapper, so any import of
|
||||
// `hooks/useSafeNavigate` in test code resolves to this file.
|
||||
// Tests can import `safeNavigateMock` to assert navigation calls — Jest's
|
||||
// `clearMocks: true` resets call history between tests.
|
||||
|
||||
interface SafeNavigateOptions {
|
||||
replace?: boolean;
|
||||
state?: unknown;
|
||||
newTab?: boolean;
|
||||
}
|
||||
|
||||
interface SafeNavigateTo {
|
||||
pathname?: string;
|
||||
search?: string;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
type SafeNavigateToType = string | SafeNavigateTo;
|
||||
|
||||
export const safeNavigateMock: jest.MockedFunction<
|
||||
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
|
||||
> = jest.fn();
|
||||
|
||||
export const useSafeNavigate = (): {
|
||||
safeNavigate: typeof safeNavigateMock;
|
||||
} => ({
|
||||
safeNavigate: safeNavigateMock,
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
import axios, { InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
import {
|
||||
interceptorRejected,
|
||||
interceptorsRequestBasePath,
|
||||
interceptorsRequestResponse,
|
||||
interceptorsResponse,
|
||||
} from 'api';
|
||||
import { getSigNozInstanceUrl } from 'utils/signozInstanceUrl';
|
||||
|
||||
/** Path-only base for the AI Assistant API. */
|
||||
export const AI_API_PATH = '/api/v1/assistant';
|
||||
|
||||
/** Header that tells the AI backend which SigNoz instance to query against. */
|
||||
export const SIGNOZ_URL_HEADER = 'X-SigNoz-URL';
|
||||
|
||||
/**
|
||||
* Sets `X-SigNoz-URL` on every outgoing AI Assistant request. The backend
|
||||
* needs the originating SigNoz instance URL for multi-tenant deployments;
|
||||
* when omitted it falls back to its `SIGNOZ_API_URL` env var.
|
||||
*/
|
||||
export const interceptorsRequestSigNozUrl = (
|
||||
value: InternalAxiosRequestConfig,
|
||||
): InternalAxiosRequestConfig => {
|
||||
if (value.headers) {
|
||||
value.headers[SIGNOZ_URL_HEADER] = getSigNozInstanceUrl();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* AI backend URL — sourced from the global config's `ai_assistant_url` field
|
||||
* at runtime. `useIsAIAssistantEnabled` keeps this in sync via `setAIBackendUrl`
|
||||
* whenever the config response changes; consumers (the axios instance and the
|
||||
* SSE fetch path) read it lazily so they always see the current value.
|
||||
*/
|
||||
let aiBackendUrl: string | null = null;
|
||||
|
||||
export function setAIBackendUrl(url: string | null): void {
|
||||
if (aiBackendUrl === url) {
|
||||
return;
|
||||
}
|
||||
|
||||
aiBackendUrl = url;
|
||||
AIAssistantInstance.defaults.baseURL = url ? `${url}${AI_API_PATH}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Full base URL for the AI Assistant API (host + path). Throws when the
|
||||
* config hasn't yet provided a URL — should never happen in practice
|
||||
* because `useIsAIAssistantEnabled` gates every consumer surface.
|
||||
*/
|
||||
export function getAIBaseUrl(): string {
|
||||
if (!aiBackendUrl) {
|
||||
throw new Error('AI assistant URL is not configured.');
|
||||
}
|
||||
return `${aiBackendUrl}${AI_API_PATH}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated axios instance for the AI Assistant.
|
||||
*
|
||||
* Mirrors the request/response interceptor stack of the main SigNoz axios
|
||||
* instance — most importantly `interceptorRejected`, which transparently
|
||||
* rotates the access token via `/sessions/rotate` on a 401 and replays the
|
||||
* original request. That's why we don't need any AI-specific 401 handling
|
||||
* for REST calls: this instance inherits the same flow as the rest of the
|
||||
* app for free.
|
||||
*
|
||||
* Only the SSE stream (`streamEvents`) still needs raw fetch since axios
|
||||
* doesn't expose `ReadableStream` — that path keeps its own auth wrapper.
|
||||
*/
|
||||
export const AIAssistantInstance = axios.create({});
|
||||
|
||||
AIAssistantInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
AIAssistantInstance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
AIAssistantInstance.interceptors.request.use(interceptorsRequestSigNozUrl);
|
||||
AIAssistantInstance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
interceptorRejected,
|
||||
);
|
||||
@@ -1,72 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import { getIsNoAuthMode } from 'utils/noAuthMode';
|
||||
|
||||
import { interceptorRejected } from '../index';
|
||||
|
||||
jest.mock('utils/noAuthMode', () => ({
|
||||
getIsNoAuthMode: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/v2/sessions/rotate/post', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('AppRoutes/utils', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../utils', () => ({
|
||||
Logout: jest.fn(),
|
||||
}));
|
||||
|
||||
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
|
||||
const post = require('api/v2/sessions/rotate/post').default;
|
||||
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
|
||||
const { Logout } = require('../utils');
|
||||
|
||||
describe('interceptorRejected — no-auth mode', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(axios, 'isAxiosError').mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('does NOT call rotate or Logout when no-auth mode is enabled on 401', async () => {
|
||||
(getIsNoAuthMode as jest.Mock).mockReturnValue(true);
|
||||
|
||||
const error = {
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
status: 401,
|
||||
config: { url: '/dashboards', method: 'get' },
|
||||
},
|
||||
config: { url: '/dashboards', headers: {} },
|
||||
};
|
||||
|
||||
await interceptorRejected(error as any).catch(() => {});
|
||||
|
||||
expect(post).not.toHaveBeenCalled();
|
||||
expect(Logout).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('DOES attempt rotate when no-auth mode is disabled on 401', async () => {
|
||||
(getIsNoAuthMode as jest.Mock).mockReturnValue(false);
|
||||
(post as jest.Mock).mockResolvedValue({
|
||||
data: { accessToken: 'a', refreshToken: 'b' },
|
||||
});
|
||||
|
||||
const error = {
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
status: 401,
|
||||
config: { url: '/dashboards', method: 'get' },
|
||||
},
|
||||
config: { url: '/dashboards', headers: {} },
|
||||
};
|
||||
|
||||
await interceptorRejected(error as any).catch(() => {});
|
||||
|
||||
expect(post).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,543 +0,0 @@
|
||||
/**
|
||||
* AI Assistant API client.
|
||||
*
|
||||
* Flow:
|
||||
* 1. POST /api/v1/assistant/threads → { threadId }
|
||||
* 2. POST /api/v1/assistant/threads/{threadId}/messages → { executionId }
|
||||
* 3. GET /api/v1/assistant/executions/{executionId}/events → SSE stream (closes on 'done')
|
||||
*
|
||||
* For subsequent messages in the same thread, repeat steps 2–3.
|
||||
* Approval/clarification events pause the stream; use approveExecution/clarifyExecution
|
||||
* to resume, which each return a new executionId to open a fresh SSE stream.
|
||||
*
|
||||
* Types in this file re-use the OpenAPI-generated DTOs in
|
||||
* `src/api/ai-assistant/sigNozAIAssistantAPI.schemas.ts`.
|
||||
* Local types are defined only when the UI needs a different shape — for
|
||||
* example, the SSE event union adds a literal `type` discriminator that the
|
||||
* generated event DTOs leave loose.
|
||||
*
|
||||
* REST calls go through `AIAssistantInstance` (an axios instance configured
|
||||
* with the same interceptor stack as the rest of the app) — that gives them
|
||||
* automatic 401-then-rotate behaviour for free. Only the SSE call is still
|
||||
* a raw `fetch` because axios doesn't expose `ReadableStream`; that one
|
||||
* path gets its own small auth wrapper.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import { Logout } from 'api/utils';
|
||||
import rotateSession from 'api/v2/sessions/rotate/post';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import type {
|
||||
ActionResultResponseDTO,
|
||||
ApprovalEventDTO,
|
||||
ApproveResponseDTO,
|
||||
CancelResponseDTO,
|
||||
ClarificationEventDTO,
|
||||
ClarifyResponseDTO,
|
||||
ConversationEventDTO,
|
||||
CreateMessageResponseDTO,
|
||||
CreateThreadResponseDTO,
|
||||
DoneEventDTO,
|
||||
ErrorEventDTO,
|
||||
ExecutionStateDTO,
|
||||
FeedbackRatingDTO,
|
||||
ListThreadsApiV1AssistantThreadsGetArchived,
|
||||
ListThreadsApiV1AssistantThreadsGetParams,
|
||||
MessageContextDTO,
|
||||
MessageContextDTOSource,
|
||||
MessageContextDTOType,
|
||||
MessageEventDTO,
|
||||
MessageSummaryDTO,
|
||||
RegenerateResponseDTO,
|
||||
StatusEventDTO,
|
||||
ThinkingEventDTO,
|
||||
ThreadDetailResponseDTO,
|
||||
ThreadListResponseDTO,
|
||||
ThreadSummaryDTO,
|
||||
ToolCallEventDTO,
|
||||
ToolResultEventDTO,
|
||||
} from './sigNozAIAssistantAPI.schemas';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
import {
|
||||
AIAssistantInstance,
|
||||
getAIBaseUrl,
|
||||
SIGNOZ_URL_HEADER,
|
||||
} from '../AIAPIInstance';
|
||||
import { getSigNozInstanceUrl } from 'utils/signozInstanceUrl';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SSE-only auth wrapper.
|
||||
//
|
||||
// REST calls go through `AIAssistantInstance` (axios) and get refresh-token
|
||||
// behaviour from the shared `interceptorRejected`. The SSE call has to use
|
||||
// raw `fetch` (axios can't stream a `ReadableStream`), so it can't ride that
|
||||
// interceptor — this small wrapper handles 401 at SSE open time by hitting
|
||||
// the same rotate endpoint and replaying the request once.
|
||||
//
|
||||
// In typical use a REST call (e.g. sendMessage / loadThread) precedes every
|
||||
// stream open, so axios will already have refreshed the token and `fetch`
|
||||
// just reads the fresh one from localStorage. The wrapper exists for the
|
||||
// edge case where SSE is the first call to encounter a 401.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let pendingRotate: Promise<string | null> | null = null;
|
||||
|
||||
async function rotateAccessToken(): Promise<string | null> {
|
||||
if (pendingRotate) {
|
||||
return pendingRotate;
|
||||
}
|
||||
const refreshToken = getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '';
|
||||
if (!refreshToken) {
|
||||
return null;
|
||||
}
|
||||
pendingRotate = (async (): Promise<string | null> => {
|
||||
try {
|
||||
const response = await rotateSession({ refreshToken });
|
||||
afterLogin(response.data.accessToken, response.data.refreshToken, true);
|
||||
return response.data.accessToken;
|
||||
} catch {
|
||||
Logout();
|
||||
return null;
|
||||
} finally {
|
||||
pendingRotate = null;
|
||||
}
|
||||
})();
|
||||
return pendingRotate;
|
||||
}
|
||||
|
||||
// Backoff schedule for 429 retries on SSE open. Three attempts is enough to
|
||||
// absorb the brief window between cancel→send→stream when the backend is
|
||||
// rate-limiting the burst, without making real "you're saturated" errors
|
||||
// take forever to surface.
|
||||
const SSE_429_BACKOFF_MS = [400, 1200, 2500];
|
||||
|
||||
function parseRetryAfterMs(value: string | null): number | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const seconds = Number(value);
|
||||
if (Number.isFinite(seconds)) {
|
||||
return Math.max(0, seconds * 1000);
|
||||
}
|
||||
const date = Date.parse(value);
|
||||
if (Number.isFinite(date)) {
|
||||
return Math.max(0, date - Date.now());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function fetchSSEWithAuth(
|
||||
url: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<Response> {
|
||||
const send = async (token: string | null): Promise<Response> => {
|
||||
const headers: Record<string, string> = {
|
||||
[SIGNOZ_URL_HEADER]: getSigNozInstanceUrl(),
|
||||
};
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return fetch(url, { headers, signal });
|
||||
};
|
||||
|
||||
const sendWithAuth = async (): Promise<Response> => {
|
||||
const initialToken = getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) || '';
|
||||
const res = await send(initialToken);
|
||||
if (res.status !== 401) {
|
||||
return res;
|
||||
}
|
||||
const refreshed = await rotateAccessToken();
|
||||
if (!refreshed) {
|
||||
return res;
|
||||
}
|
||||
return send(refreshed);
|
||||
};
|
||||
|
||||
let res = await sendWithAuth();
|
||||
for (const baseDelay of SSE_429_BACKOFF_MS) {
|
||||
if (res.status !== 429 || signal?.aborted) {
|
||||
return res;
|
||||
}
|
||||
const retryAfter = parseRetryAfterMs(res.headers.get('Retry-After'));
|
||||
const delay = retryAfter ?? baseDelay;
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(resolve, delay);
|
||||
signal?.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
clearTimeout(timer);
|
||||
reject(new DOMException('SSE 429 backoff aborted', 'AbortError'));
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
res = await sendWithAuth();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SSE event types
|
||||
//
|
||||
// The generated event DTOs each declare `type?: string` (loose). The UI needs
|
||||
// a discriminated union, so we intersect each variant with a string-literal
|
||||
// `type` to enable narrowing via `event.type === 'status'`.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type SSEEvent =
|
||||
| (StatusEventDTO & { type: 'status' })
|
||||
| (MessageEventDTO & { type: 'message' })
|
||||
| (ThinkingEventDTO & { type: 'thinking' })
|
||||
| (ToolCallEventDTO & { type: 'tool_call' })
|
||||
| (ToolResultEventDTO & { type: 'tool_result' })
|
||||
| (ApprovalEventDTO & { type: 'approval' })
|
||||
| (ClarificationEventDTO & { type: 'clarification' })
|
||||
| (ErrorEventDTO & { type: 'error' })
|
||||
| (ConversationEventDTO & { type: 'conversation' })
|
||||
| (DoneEventDTO & { type: 'done' });
|
||||
|
||||
/** String-literal view of `ExecutionStateDTO` for ergonomic comparisons. */
|
||||
export type ExecutionState = `${ExecutionStateDTO}`;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Re-exported DTOs — the wire shape, used directly without remapping.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type ThreadSummary = ThreadSummaryDTO;
|
||||
export type ThreadListResponse = ThreadListResponseDTO;
|
||||
export type ThreadDetailResponse = ThreadDetailResponseDTO;
|
||||
export type MessageSummary = MessageSummaryDTO;
|
||||
export type CancelResponse = CancelResponseDTO;
|
||||
|
||||
/**
|
||||
* Construction-friendly view of `MessageContextDTO`: enum fields are widened
|
||||
* to their string-literal unions so call-sites can pass `'mention'` instead
|
||||
* of `MessageContextDTOSource.mention`.
|
||||
*/
|
||||
export type MessageContext = Omit<MessageContextDTO, 'source' | 'type'> & {
|
||||
source: `${MessageContextDTOSource}`;
|
||||
type: `${MessageContextDTOType}`;
|
||||
};
|
||||
|
||||
/** Construction-friendly view of `ListThreadsApiV1AssistantThreadsGetParams`. */
|
||||
export type ListThreadsOptions = Omit<
|
||||
ListThreadsApiV1AssistantThreadsGetParams,
|
||||
'archived'
|
||||
> & {
|
||||
archived?: `${ListThreadsApiV1AssistantThreadsGetArchived}`;
|
||||
};
|
||||
|
||||
/** String-literal view of `FeedbackRatingDTO` so call-sites can pass `'positive'`/`'negative'`. */
|
||||
export type FeedbackRating = `${FeedbackRatingDTO}`;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Thread listing & detail
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function listThreads(
|
||||
options: ListThreadsOptions = {},
|
||||
): Promise<ThreadListResponse> {
|
||||
const {
|
||||
archived = 'false',
|
||||
limit = 20,
|
||||
cursor = null,
|
||||
sort = 'updated_desc',
|
||||
} = options;
|
||||
const response = await AIAssistantInstance.get<ThreadListResponse>(
|
||||
'/threads',
|
||||
{
|
||||
params: {
|
||||
archived,
|
||||
limit,
|
||||
sort,
|
||||
...(cursor ? { cursor } : {}),
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateThread(
|
||||
threadId: string,
|
||||
update: { title?: string | null; archived?: boolean | null },
|
||||
): Promise<ThreadSummary> {
|
||||
const response = await AIAssistantInstance.patch<ThreadSummary>(
|
||||
`/threads/${threadId}`,
|
||||
update,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getThreadDetail(
|
||||
threadId: string,
|
||||
): Promise<ThreadDetailResponse> {
|
||||
const response = await AIAssistantInstance.get<ThreadDetailResponse>(
|
||||
`/threads/${threadId}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 1 — Create thread
|
||||
// POST /api/v1/assistant/threads → { threadId }
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function createThread(signal?: AbortSignal): Promise<string> {
|
||||
const response = await AIAssistantInstance.post<CreateThreadResponseDTO>(
|
||||
'/threads',
|
||||
{},
|
||||
{ signal },
|
||||
);
|
||||
return response.data.threadId;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 2 — Send message
|
||||
// POST /api/v1/assistant/threads/{threadId}/messages → { executionId }
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Fetches the thread's active executionId for reconnect on thread_busy (409). */
|
||||
async function getActiveExecutionId(threadId: string): Promise<string | null> {
|
||||
try {
|
||||
const response = await AIAssistantInstance.get<ThreadDetailResponseDTO>(
|
||||
`/threads/${threadId}`,
|
||||
);
|
||||
return response.data.activeExecutionId ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendMessage(
|
||||
threadId: string,
|
||||
content: string,
|
||||
contexts?: MessageContext[],
|
||||
signal?: AbortSignal,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const response = await AIAssistantInstance.post<CreateMessageResponseDTO>(
|
||||
`/threads/${threadId}/messages`,
|
||||
{
|
||||
content,
|
||||
...(contexts && contexts.length > 0 ? { contexts } : {}),
|
||||
},
|
||||
{ signal },
|
||||
);
|
||||
return response.data.executionId;
|
||||
} catch (err) {
|
||||
// Thread already has an active execution — reconnect to it instead of
|
||||
// failing the user's send.
|
||||
if (axios.isAxiosError(err) && err.response?.status === 409) {
|
||||
const executionId = await getActiveExecutionId(threadId);
|
||||
if (executionId) {
|
||||
return executionId;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step 3 — Stream execution events
|
||||
// GET /api/v1/assistant/executions/{executionId}/events → SSE
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function parseSSELine(line: string): SSEEvent | null {
|
||||
if (!line.startsWith('data: ')) {
|
||||
return null;
|
||||
}
|
||||
const json = line.slice('data: '.length).trim();
|
||||
if (!json || json === '[DONE]') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(json) as SSEEvent;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseSSEChunk(chunk: string): SSEEvent[] {
|
||||
return chunk
|
||||
.split('\n\n')
|
||||
.map((part) => part.split('\n').find((l) => l.startsWith('data: ')) ?? '')
|
||||
.map(parseSSELine)
|
||||
.filter((e): e is SSEEvent => e !== null);
|
||||
}
|
||||
|
||||
async function* readSSEReader(
|
||||
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||
): AsyncGenerator<SSEEvent> {
|
||||
const decoder = new TextDecoder();
|
||||
let lineBuffer = '';
|
||||
try {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
lineBuffer += decoder.decode(value, { stream: true });
|
||||
const parts = lineBuffer.split('\n\n');
|
||||
lineBuffer = parts.pop() ?? '';
|
||||
yield* parts.flatMap(parseSSEChunk);
|
||||
}
|
||||
yield* parseSSEChunk(lineBuffer);
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by `streamEvents` when the SSE open returns a non-2xx response.
|
||||
* Carries the HTTP status so callers can branch on rate-limit vs. other
|
||||
* failures (e.g. show a "please wait a moment" message on 429).
|
||||
*/
|
||||
export class SSEStreamError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(status: number, statusText: string) {
|
||||
super(`SSE stream failed: ${status} ${statusText}`);
|
||||
this.name = 'SSEStreamError';
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export async function* streamEvents(
|
||||
executionId: string,
|
||||
signal?: AbortSignal,
|
||||
): AsyncGenerator<SSEEvent> {
|
||||
const res = await fetchSSEWithAuth(
|
||||
`${getAIBaseUrl()}/executions/${executionId}/events`,
|
||||
signal,
|
||||
);
|
||||
if (!res.ok || !res.body) {
|
||||
throw new SSEStreamError(res.status, res.statusText);
|
||||
}
|
||||
yield* readSSEReader(res.body.getReader());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Approval / Clarification / Cancel actions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Approve a pending action. Returns a new executionId — open a fresh SSE stream for it. */
|
||||
export async function approveExecution(
|
||||
approvalId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<string> {
|
||||
const response = await AIAssistantInstance.post<ApproveResponseDTO>(
|
||||
'/approve',
|
||||
{ approvalId },
|
||||
{ signal },
|
||||
);
|
||||
return response.data.executionId;
|
||||
}
|
||||
|
||||
/** Reject a pending action. */
|
||||
export async function rejectExecution(
|
||||
approvalId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
await AIAssistantInstance.post('/reject', { approvalId }, { signal });
|
||||
}
|
||||
|
||||
/** Submit clarification answers. Returns a new executionId — open a fresh SSE stream for it. */
|
||||
export async function clarifyExecution(
|
||||
clarificationId: string,
|
||||
answers: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<string> {
|
||||
const response = await AIAssistantInstance.post<ClarifyResponseDTO>(
|
||||
'/clarify',
|
||||
{ clarificationId, answers },
|
||||
{ signal },
|
||||
);
|
||||
return response.data.executionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean-slate regeneration of an assistant response. The backend rewinds the
|
||||
* conversation up to (excluding) the supplied messageId and starts a fresh
|
||||
* execution. Returns the new executionId — open an SSE stream for it the
|
||||
* same way `sendMessage` and `approve` do.
|
||||
*/
|
||||
export async function regenerateMessage(
|
||||
messageId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<string> {
|
||||
const response = await AIAssistantInstance.post<RegenerateResponseDTO>(
|
||||
`/messages/${messageId}/regenerate`,
|
||||
undefined,
|
||||
{ signal },
|
||||
);
|
||||
return response.data.executionId;
|
||||
}
|
||||
|
||||
export async function cancelExecution(
|
||||
threadId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<CancelResponse> {
|
||||
const response = await AIAssistantInstance.post<CancelResponse>(
|
||||
'/cancel',
|
||||
{ threadId },
|
||||
{ signal },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rollback actions — undo / revert / restore
|
||||
// All three POST `{ actionMetadataId }` and return `ActionResultResponseDTO`.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function postRollback(
|
||||
endpoint: 'undo' | 'revert' | 'restore',
|
||||
actionMetadataId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ActionResultResponseDTO> {
|
||||
const response = await AIAssistantInstance.post<ActionResultResponseDTO>(
|
||||
`/${endpoint}`,
|
||||
{ actionMetadataId },
|
||||
{ signal },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const undoExecution = (
|
||||
actionMetadataId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ActionResultResponseDTO> =>
|
||||
postRollback('undo', actionMetadataId, signal);
|
||||
|
||||
export const revertExecution = (
|
||||
actionMetadataId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ActionResultResponseDTO> =>
|
||||
postRollback('revert', actionMetadataId, signal);
|
||||
|
||||
export const restoreExecution = (
|
||||
actionMetadataId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ActionResultResponseDTO> =>
|
||||
postRollback('restore', actionMetadataId, signal);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Feedback
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function submitFeedback(
|
||||
messageId: string,
|
||||
rating: FeedbackRating,
|
||||
comment?: string,
|
||||
): Promise<void> {
|
||||
await AIAssistantInstance.post(`/messages/${messageId}/feedback`, {
|
||||
rating,
|
||||
comment: comment ?? null,
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user