Compare commits

..

27 Commits

Author SHA1 Message Date
SagarRajput-7
b9bcd2d4e1 feat: refactored the member status mapping 2026-03-18 23:42:53 +05:30
SagarRajput-7
34dcd79243 feat: updated the confirmation dialog description as now the we cant permanently delete the member 2026-03-18 21:06:43 +05:30
SagarRajput-7
145d6327a7 feat: feedback, refactor and test mock update 2026-03-18 20:29:11 +05:30
SagarRajput-7
61cfd33fc6 feat: test case and pagination fix 2026-03-18 20:29:11 +05:30
SagarRajput-7
b299d63263 feat: changed text for copy, cancel and ingeneral messaging for invited users 2026-03-18 20:29:11 +05:30
SagarRajput-7
94f3e6d6d7 feat: delete orphaned type files 2026-03-18 20:29:11 +05:30
SagarRajput-7
7ae0a23103 feat: removed deprecated invite endpoint apis 2026-03-18 20:29:11 +05:30
SagarRajput-7
2d91b5fd0b feat: updated members page with new status response and remove invite endpoint api 2026-03-18 20:29:11 +05:30
Karan Balani
ab1428d413 fix: allow pending user to be updated 2026-03-18 20:28:02 +05:30
Karan Balani
9c859e4d07 chore: add back validation for pending user in list user apis for integration tests 2026-03-18 20:25:39 +05:30
Karan Balani
d6de4d58f7 chore: deprecate old user invite apis 2026-03-18 20:25:39 +05:30
Vinicius Lourenço
e52c5683dd feat(signozhq-ui): add @signozhq/ui lib (#10616) 2026-03-18 13:44:25 +00:00
Abhi kumar
90e3cb6775 feat: replaced external apis barchart with the new bar chart (#10460)
* feat: replaced external apis barchart with the new bar chart

* fix: tests

* chore: fixed tsc
2026-03-18 13:36:23 +00:00
primus-bot[bot]
155f287462 chore(release): bump to v0.116.1 (#10635)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-03-18 12:28:33 +00:00
Piyush Singariya
c8fcc48022 Revert "fix: "In Progress" stuck agent config (#10476)" (#10633)
This reverts commit fd19ff8e5e.
2026-03-18 11:30:39 +00:00
Vikrant Gupta
44b6885639 fix(identn): identn provider claims (#10631)
* fix(identn): identn provider claims

* fix(identn): add integration tests

* fix(identn): use identn provider from claims
2026-03-18 11:23:50 +00:00
Piyush Singariya
0e5a128325 refactor: consolidate body column for JSON logs (#10325)
* feat: has JSON QB

* fix: tests expected queries and values

* fix: ignored .vscode in gitignore

* fix: tests GroupBy

* revert: gitignore change

* fix: build json plans in metadata

* fix: empty filteredArrays condition

* fix: tests

* fix: tests

* fix: json qb test fix

* fix: review based on tushar

* fix: changes based on review from Srikanth

* fix: remove unnecessary bool checking

* fix: removed comment

* fix: merge json body columns together

* chore: var renamed

* fix: merge conflict

* test: fix

* fix: tests

* fix: go test flakiness

* chore: merge json fields

* fix: handle datatype collision

* revert: few unrelated changes

* revert: more unrelated change

* test: blocked on pr #10153

* feat: mapping body_v2.message:string map to body

* fix: go.mod required changes

* fix: remove unused function

* fix: test fixed

* fix: go mod changes

* fix: tests

* fix: go lint

* revert: remvoing unused function

* revert: change ReadMultiple is needed

* fix: body.message not being mapped correctly

* fix: append warnings from fieldkeys

* fix: change warning to a const to fix tests

* chore: addressing comments from Nitya

* chore: remove unnecessary change

* fix: shift warning attachment to getKeySelectors

* fix: lint error

* feat: update message as typehint in JSON Column (#10545)

* fix: cursor comments

* chore: minor changes based on review

* fix: message field key search in JSON Logs (#10577)

* feat: work in progress

* fix: test run success

* fix: in progress

* fix: excluding message from metadata fetch

* test: cleared

* fix: key name in metadata

* fix: uncomment tests

* chore: change to method for staticfields

* fix: remove confusing comments; remove usage of logical keyword

* chore: shift method above business logic

* chore: changes based on review

* fix: comments in metadata_store.go

* fix: fallback expr switch case

* revert: remove unused JSON Field datatype

* fix: remove the exception checking

* chore: keep message contained to field mapper

* chore: text search tests

* fix: package test fixed

* fix: redundant code block removal

* fix: retain staticfield implementation and spell fix

* fix: nil param lint

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
2026-03-18 10:48:17 +00:00
Piyush Singariya
fd19ff8e5e fix: "In Progress" stuck agent config (#10476)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* fix: in progress status stuck in logs pipelines

* fix: stuck in progress logs pipeline status

* fix: changes based on review

* revert: comment change

* fix: change order of handling updation

* fix: check newstatus deploy status
2026-03-18 08:31:26 +00:00
swapnil-signoz
7b9e93162f feat: adding cloud integration type for refactor (#10453)
* feat: adding cloud integration type for refactor

* refactor: store interfaces to use local types and error

* feat: adding updated types for cloud integration

* refactor: using struct for map

* refactor: update cloud integration types and module interface

* fix: correct GetService signature and remove shadowed Data field

* refactor: adding comments and removed wrong code

* refactor: streamlining types

* refactor: add comments for backward compatibility in PostableAgentCheckInRequest

* refactor: update Dashboard struct comments and remove unused fields

* refactor: clean up types

* refactor: renaming service type to service id

* refactor: using serviceID type

* feat: adding method for service id creation

* refactor: updating store methods

* refactor: clean up

* refactor: review comments
2026-03-18 08:20:18 +00:00
primus-bot[bot]
f106f57097 chore(release): bump to v0.116.0 (#10626)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-03-18 06:47:16 +00:00
Vikrant Gupta
5bafdeb373 fix(user): add config for user invite token expiry (#10618)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* fix(user): increase expiry for reset password token for invites

* fix(user): increase expiry for reset password token for invites

* fix(user): increase expiry for reset password token for invites

* fix(user): increase expiry for reset password token for invites
2026-03-17 16:57:29 +00:00
Naman Verma
24b72084ac fix: return not-found error with diagnostic info for absent metrics (#10560)
* fix: check for metric type without query range constraint

* revert: revert check for metric type without query range constraint

* chore: move temporality+type fetcher to the case where it is actually used

* fix: don't send absent metrics to query builder

* chore: better package import name

* test: unit test add mock for metadata call (which is expected in the test's scenario)

* revert: revert seeding of absent metrics

* fix: throw a not found err if metric data is missing

* test: unit test add mock for metadata call (which is expected in the test's scenario)

* revert: no need for special err handling in threshold rule

* chore: add last seen info in err message

* test: fix broken dashboard test

* test: integration test for short time range query

* chore: python lint issue
2026-03-17 16:15:32 +00:00
Pandey
2db83b453d refactor: merge roletypes into authtypes (#10614)
* refactor: merge roletypes into authtypes

* refactor: merge roletypes into authtypes

* refactor: update openapi spec

* feat: split CI

* fix: fix tsc of frontend
2026-03-17 15:43:58 +00:00
Amaresh S M
2f012715b4 fix(frontend/vite): avoid inlining whole process.env into bundle (#10605) 2026-03-17 11:51:30 +00:00
Vikrant Gupta
aa05a7bf14 chore(identn): add me as codeowner for identn (#10612) 2026-03-17 11:29:34 +00:00
Vikrant Gupta
99327960b0 feat(authn): move identn to factory and config (#10608)
* feat(authn): move identn to factory and config

* feat(authn): add support for enabling identNs

* feat(authn): add support for enabling identNs
2026-03-17 11:22:26 +00:00
Pandey
12b02a1002 feat(sqlschema): add support for partial unique indexes (#10604)
* feat(sqlschema): add support for partial unique indexes

* feat(sqlschema): add support for multiple indexes

* feat(sqlschema): add support for multiple indexes

* feat(sqlschema): move normalizer to its own struct

* feat(sqlschema): move normalizer tests to normalizer

* feat(sqlschema): move normalizer tests to normalizer

* feat(sqlschema): add more index tests from docs
2026-03-17 11:22:11 +00:00
154 changed files with 4015 additions and 3222 deletions

4
.github/CODEOWNERS vendored
View File

@@ -105,6 +105,10 @@ go.mod @therealpandey
/pkg/modules/authdomain/ @vikrantgupta25
/pkg/modules/role/ @vikrantgupta25
# IdentN Owners
/pkg/identn/ @vikrantgupta25
/pkg/http/middleware/identn.go @vikrantgupta25
# Integration tests
/tests/integration/ @vikrantgupta25

View File

@@ -102,13 +102,3 @@ jobs:
run: |
go run cmd/enterprise/*.go generate openapi
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in openapi spec. Run go run cmd/enterprise/*.go generate openapi locally and commit."; exit 1)
- name: node-install
uses: actions/setup-node@v5
with:
node-version: "22"
- name: install-frontend
run: cd frontend && yarn install
- name: generate-api-clients
run: |
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)

View File

@@ -52,16 +52,16 @@ jobs:
with:
PRIMUS_REF: main
JS_SRC: frontend
md-languages:
languages:
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: checkout
- name: self-checkout
uses: actions/checkout@v4
- name: validate md languages
- name: run
run: bash frontend/scripts/validate-md-languages.sh
authz:
if: |
@@ -70,44 +70,55 @@ jobs:
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
runs-on: ubuntu-latest
steps:
- name: Checkout code
- name: self-checkout
uses: actions/checkout@v5
- name: Set up Node.js
- name: node-install
uses: actions/setup-node@v5
with:
node-version: "22"
- name: Install frontend dependencies
- name: deps-install
working-directory: ./frontend
run: |
yarn install
- name: Install uv
- name: uv-install
uses: astral-sh/setup-uv@v5
- name: Install Python dependencies
- name: uv-deps
working-directory: ./tests/integration
run: |
uv sync
- name: Start test environment
- name: setup-test
run: |
make py-test-setup
- name: Generate permissions.type.ts
- name: generate
working-directory: ./frontend
run: |
yarn generate:permissions-type
- name: Teardown test environment
- name: teardown-test
if: always()
run: |
make py-test-teardown
- name: Check for changes
- name: validate
run: |
if ! git diff --exit-code frontend/src/hooks/useAuthZ/permissions.type.ts; then
echo "::error::frontend/src/hooks/useAuthZ/permissions.type.ts is out of date. Please run the generator locally and commit the changes: npm run generate:permissions-type (from the frontend directory)"
exit 1
fi
openapi:
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-frontend
run: cd frontend && yarn install
- name: generate-api-clients
run: |
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)

View File

@@ -18,7 +18,6 @@ import (
"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/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/queryparser"
@@ -74,8 +73,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
},
signoz.NewSQLStoreProviderFactories(),
signoz.NewTelemetryStoreProviderFactories(),
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userGetter user.Getter) (map[authtypes.AuthNProvider]authn.AuthN, error) {
return signoz.NewAuthNs(ctx, providerSettings, store, licensing, userGetter)
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)
},
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))

View File

@@ -9,12 +9,12 @@ import (
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
"github.com/SigNoz/signoz/ee/authz/openfgaauthz"
eequerier "github.com/SigNoz/signoz/ee/querier"
"github.com/SigNoz/signoz/ee/authz/openfgaschema"
"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/modules/dashboard/impldashboard"
eequerier "github.com/SigNoz/signoz/ee/querier"
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
@@ -29,7 +29,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/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/signoz"
@@ -96,7 +95,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
},
sqlstoreFactories,
signoz.NewTelemetryStoreProviderFactories(),
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userGetter user.Getter) (map[authtypes.AuthNProvider]authn.AuthN, error) {
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)
if err != nil {
return nil, err
@@ -107,7 +106,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return nil, err
}
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing, userGetter)
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing)
if err != nil {
return nil, err
}

View File

@@ -308,6 +308,9 @@ user:
allow_self: true
# The duration within which a user can reset their password.
max_token_lifetime: 6h
invite:
# The duration within which a user can accept their invite.
max_token_lifetime: 48h
root:
# Whether to enable the root user. When enabled, a root user is provisioned
# on startup using the email and password below. The root user cannot be
@@ -321,3 +324,19 @@ user:
org:
name: default
id: 00000000-0000-0000-0000-000000000000
##################### IdentN #####################
identn:
tokenizer:
# toggle the identN resolver
enabled: true
# headers to use for tokenizer identN resolver
headers:
- Authorization
- Sec-WebSocket-Protocol
apikey:
# toggle the identN resolver
enabled: true
# headers to use for apikey identN resolver
headers:
- SIGNOZ-API-KEY

View File

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

View File

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

View File

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

View File

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

View File

@@ -1984,6 +1984,52 @@ components:
type: string
type: array
type: object
TypesChangePasswordRequest:
properties:
newPassword:
type: string
oldPassword:
type: string
userId:
type: string
type: object
TypesGettableAPIKey:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
createdByUser:
$ref: '#/components/schemas/TypesUser'
expiresAt:
format: int64
type: integer
id:
type: string
lastUsed:
format: int64
type: integer
name:
type: string
revoked:
type: boolean
role:
type: string
token:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
updatedByUser:
$ref: '#/components/schemas/TypesUser'
userId:
type: string
required:
- id
type: object
TypesGettableGlobalConfig:
properties:
external_url:
@@ -1998,6 +2044,31 @@ components:
required:
- id
type: object
TypesInvite:
properties:
createdAt:
format: date-time
type: string
email:
type: string
id:
type: string
inviteLink:
type: string
name:
type: string
orgId:
type: string
role:
type: string
token:
type: string
updatedAt:
format: date-time
type: string
required:
- id
type: object
TypesOrganization:
properties:
alias:
@@ -2020,78 +2091,7 @@ components:
required:
- id
type: object
UsertypesChangePasswordRequest:
properties:
newPassword:
type: string
oldPassword:
type: string
userId:
type: string
type: object
UsertypesGettableAPIKey:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
createdByUser:
$ref: '#/components/schemas/UsertypesUser'
expiresAt:
format: int64
type: integer
id:
type: string
lastUsed:
format: int64
type: integer
name:
type: string
revoked:
type: boolean
role:
type: string
token:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
updatedByUser:
$ref: '#/components/schemas/UsertypesUser'
userId:
type: string
required:
- id
type: object
UsertypesInvite:
properties:
createdAt:
format: date-time
type: string
email:
type: string
id:
type: string
inviteLink:
type: string
name:
type: string
orgId:
type: string
role:
type: string
token:
type: string
updatedAt:
format: date-time
type: string
required:
- id
type: object
UsertypesPostableAPIKey:
TypesPostableAPIKey:
properties:
expiresInDays:
format: int64
@@ -2101,27 +2101,16 @@ components:
role:
type: string
type: object
UsertypesPostableAcceptInvite:
properties:
displayName:
type: string
password:
type: string
sourceUrl:
type: string
token:
type: string
type: object
UsertypesPostableBulkInviteRequest:
TypesPostableBulkInviteRequest:
properties:
invites:
items:
$ref: '#/components/schemas/UsertypesPostableInvite'
$ref: '#/components/schemas/TypesPostableInvite'
type: array
required:
- invites
type: object
UsertypesPostableForgotPassword:
TypesPostableForgotPassword:
properties:
email:
type: string
@@ -2133,7 +2122,7 @@ components:
- orgId
- email
type: object
UsertypesPostableInvite:
TypesPostableInvite:
properties:
email:
type: string
@@ -2144,14 +2133,14 @@ components:
role:
type: string
type: object
UsertypesPostableResetPassword:
TypesPostableResetPassword:
properties:
password:
type: string
token:
type: string
type: object
UsertypesResetPasswordToken:
TypesResetPasswordToken:
properties:
expiresAt:
format: date-time
@@ -2165,7 +2154,7 @@ components:
required:
- id
type: object
UsertypesStorableAPIKey:
TypesStorableAPIKey:
properties:
createdAt:
format: date-time
@@ -2192,7 +2181,7 @@ components:
required:
- id
type: object
UsertypesUser:
TypesUser:
properties:
createdAt:
format: date-time
@@ -2392,7 +2381,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesChangePasswordRequest'
$ref: '#/components/schemas/TypesChangePasswordRequest'
responses:
"204":
description: No Content
@@ -3197,7 +3186,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesResetPasswordToken'
$ref: '#/components/schemas/TypesResetPasswordToken'
status:
type: string
required:
@@ -3290,53 +3279,6 @@ paths:
tags:
- global
/api/v1/invite:
get:
deprecated: false
description: This endpoint lists all invites
operationId: ListInvite
responses:
"200":
content:
application/json:
schema:
properties:
data:
items:
$ref: '#/components/schemas/UsertypesInvite'
type: array
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: List invites
tags:
- users
post:
deprecated: false
description: This endpoint creates an invite for a user
@@ -3345,7 +3287,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesPostableInvite'
$ref: '#/components/schemas/TypesPostableInvite'
responses:
"201":
content:
@@ -3353,7 +3295,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesInvite'
$ref: '#/components/schemas/TypesInvite'
status:
type: string
required:
@@ -3399,151 +3341,6 @@ paths:
summary: Create invite
tags:
- users
/api/v1/invite/{id}:
delete:
deprecated: false
description: This endpoint deletes an invite by id
operationId: DeleteInvite
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete invite
tags:
- users
/api/v1/invite/{token}:
get:
deprecated: false
description: This endpoint gets an invite by token
operationId: GetInvite
parameters:
- in: path
name: token
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesInvite'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
summary: Get invite
tags:
- users
/api/v1/invite/accept:
post:
deprecated: false
description: This endpoint accepts an invite by token
operationId: AcceptInvite
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesPostableAcceptInvite'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesUser'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
summary: Accept invite
tags:
- users
/api/v1/invite/bulk:
post:
deprecated: false
@@ -3553,7 +3350,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesPostableBulkInviteRequest'
$ref: '#/components/schemas/TypesPostableBulkInviteRequest'
responses:
"201":
description: Created
@@ -3878,7 +3675,7 @@ paths:
properties:
data:
items:
$ref: '#/components/schemas/UsertypesGettableAPIKey'
$ref: '#/components/schemas/TypesGettableAPIKey'
type: array
status:
type: string
@@ -3921,7 +3718,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesPostableAPIKey'
$ref: '#/components/schemas/TypesPostableAPIKey'
responses:
"201":
content:
@@ -3929,7 +3726,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesGettableAPIKey'
$ref: '#/components/schemas/TypesGettableAPIKey'
status:
type: string
required:
@@ -4035,7 +3832,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesStorableAPIKey'
$ref: '#/components/schemas/TypesStorableAPIKey'
responses:
"204":
content:
@@ -4196,7 +3993,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesPostableResetPassword'
$ref: '#/components/schemas/TypesPostableResetPassword'
responses:
"204":
description: No Content
@@ -5271,7 +5068,7 @@ paths:
properties:
data:
items:
$ref: '#/components/schemas/UsertypesUser'
$ref: '#/components/schemas/TypesUser'
type: array
status:
type: string
@@ -5369,7 +5166,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesUser'
$ref: '#/components/schemas/TypesUser'
status:
type: string
required:
@@ -5423,7 +5220,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesUser'
$ref: '#/components/schemas/TypesUser'
responses:
"200":
content:
@@ -5431,7 +5228,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesUser'
$ref: '#/components/schemas/TypesUser'
status:
type: string
required:
@@ -5489,7 +5286,7 @@ paths:
schema:
properties:
data:
$ref: '#/components/schemas/UsertypesUser'
$ref: '#/components/schemas/TypesUser'
status:
type: string
required:
@@ -5692,7 +5489,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UsertypesPostableForgotPassword'
$ref: '#/components/schemas/TypesPostableForgotPassword'
responses:
"204":
description: No Content

View File

@@ -213,7 +213,7 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
return module.pkgDashboardModule.Update(ctx, orgID, id, updatedBy, data, diff)
}
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role authtypes.LegacyRole, lock bool) error {
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, role, lock)
}

View File

@@ -14,8 +14,8 @@ import (
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/user"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
"log/slog"
@@ -142,10 +142,10 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
"cloud_provider", cloudProvider,
)
newPAT, err := usertypes.NewStorableAPIKey(
newPAT, err := types.NewStorableAPIKey(
integrationPATName,
integrationUser.ID,
authtypes.RoleViewer,
types.RoleViewer,
0,
)
if err != nil {
@@ -165,16 +165,16 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
ctx context.Context, orgId string, cloudProvider string,
) (*usertypes.User, *basemodel.ApiError) {
) (*types.User, *basemodel.ApiError) {
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider)
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
cloudIntegrationUser, err := usertypes.NewUser(cloudIntegrationUserName, email, authtypes.RoleViewer, valuer.MustNewUUID(orgId), usertypes.UserStatusActive)
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId), types.UserStatusActive)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
}
password := usertypes.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
password := types.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
cloudIntegrationUser, err = ah.Signoz.Modules.User.GetOrCreateUser(ctx, cloudIntegrationUser, user.WithFactorPassword(password))
if err != nil {

View File

@@ -80,6 +80,21 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
alertDataRows := cmock.NewRows(cols, tc.Values)
mock := telemetryStore.Mock()
// Mock metadata queries for FetchTemporalityAndTypeMulti
// First query: fetchMetricsTemporalityAndType (from signoz_metrics time series table)
metadataCols := []cmock.ColumnType{
{Name: "metric_name", Type: "String"},
{Name: "temporality", Type: "String"},
{Name: "type", Type: "String"},
{Name: "is_monotonic", Type: "Bool"},
}
metadataRows := cmock.NewRows(metadataCols, [][]any{
{"probe_success", metrictypes.Unspecified, metrictypes.GaugeType, false},
})
mock.ExpectQuery("*distributed_time_series_v4*").WithArgs(nil, nil, nil).WillReturnRows(metadataRows)
// Second query: fetchMeterSourceMetricsTemporalityAndType (from signoz_meter table)
emptyMetadataRows := cmock.NewRows(metadataCols, [][]any{})
mock.ExpectQuery("*meter*").WithArgs(nil).WillReturnRows(emptyMetadataRows)
// Generate query arguments for the metric query
evalTime := time.Now().UTC()

View File

@@ -223,7 +223,8 @@ SELECT
i.indisunique AS unique,
i.indisprimary AS primary,
a.attname AS column_name,
array_position(i.indkey, a.attnum) AS column_position
array_position(i.indkey, a.attnum) AS column_position,
pg_get_expr(i.indpred, i.indrelid) AS predicate
FROM
pg_index i
LEFT JOIN pg_class ct ON ct.oid = i.indrelid
@@ -246,7 +247,12 @@ ORDER BY index_name, column_position`, string(name))
}
}()
uniqueIndicesMap := make(map[string]*sqlschema.UniqueIndex)
type indexEntry struct {
columns []sqlschema.ColumnName
predicate *string
}
uniqueIndicesMap := make(map[string]*indexEntry)
for rows.Next() {
var (
tableName string
@@ -256,30 +262,50 @@ ORDER BY index_name, column_position`, string(name))
columnName string
// starts from 0 and is unused in this function, this is to ensure that the column names are in the correct order
columnPosition int
predicate *string
)
if err := rows.Scan(&tableName, &indexName, &unique, &primary, &columnName, &columnPosition); err != nil {
if err := rows.Scan(&tableName, &indexName, &unique, &primary, &columnName, &columnPosition, &predicate); err != nil {
return nil, err
}
if unique {
if _, ok := uniqueIndicesMap[indexName]; !ok {
uniqueIndicesMap[indexName] = &sqlschema.UniqueIndex{
TableName: name,
ColumnNames: []sqlschema.ColumnName{sqlschema.ColumnName(columnName)},
uniqueIndicesMap[indexName] = &indexEntry{
columns: []sqlschema.ColumnName{sqlschema.ColumnName(columnName)},
predicate: predicate,
}
} else {
uniqueIndicesMap[indexName].ColumnNames = append(uniqueIndicesMap[indexName].ColumnNames, sqlschema.ColumnName(columnName))
uniqueIndicesMap[indexName].columns = append(uniqueIndicesMap[indexName].columns, sqlschema.ColumnName(columnName))
}
}
}
indices := make([]sqlschema.Index, 0)
for indexName, index := range uniqueIndicesMap {
if index.Name() == indexName {
indices = append(indices, index)
for indexName, entry := range uniqueIndicesMap {
if entry.predicate != nil {
index := &sqlschema.PartialUniqueIndex{
TableName: name,
ColumnNames: entry.columns,
Where: *entry.predicate,
}
if index.Name() == indexName {
indices = append(indices, index)
} else {
indices = append(indices, index.Named(indexName))
}
} else {
indices = append(indices, index.Named(indexName))
index := &sqlschema.UniqueIndex{
TableName: name,
ColumnNames: entry.columns,
}
if index.Name() == indexName {
indices = append(indices, index)
} else {
indices = append(indices, index.Named(indexName))
}
}
}

View File

@@ -24,7 +24,8 @@ const config: Config.InitialOptions = {
'<rootDir>/node_modules/@signozhq/icons/dist/index.esm.js',
'^react-syntax-highlighter/dist/esm/(.*)$':
'<rootDir>/node_modules/react-syntax-highlighter/dist/cjs/$1',
'^@signozhq/([^/]+)$': '<rootDir>/node_modules/@signozhq/$1/dist/$1.js',
'^@signozhq/(?!ui$)([^/]+)$':
'<rootDir>/node_modules/@signozhq/$1/dist/$1.js',
},
extensionsToTreatAsEsm: ['.ts'],
testMatch: ['<rootDir>/src/**/*?(*.)(test).(ts|js)?(x)'],

View File

@@ -11,6 +11,7 @@
"prettify": "prettier --write .",
"fmt": "prettier --check .",
"lint": "eslint ./src",
"lint:generated": "eslint ./src/api/generated --fix",
"lint:fix": "eslint ./src --fix",
"jest": "jest",
"jest:coverage": "jest --coverage",
@@ -66,6 +67,7 @@
"@signozhq/table": "0.3.7",
"@signozhq/toggle-group": "0.0.1",
"@signozhq/tooltip": "0.0.2",
"@signozhq/ui": "0.0.5",
"@tanstack/react-table": "8.20.6",
"@tanstack/react-virtual": "3.11.2",
"@uiw/codemirror-theme-copilot": "4.23.11",
@@ -283,4 +285,4 @@
"tmp": "0.2.4",
"vite": "npm:rolldown-vite@7.3.1"
}
}
}

View File

@@ -25,7 +25,7 @@ echo "\n✅ Prettier formatting successful"
# Fix linting issues
echo "\n\n---\nRunning eslint...\n"
if ! yarn lint --fix --quiet src/api/generated; then
if ! yarn lint:generated; then
echo "ESLint check failed! Please fix linting errors before proceeding."
exit 1
fi

View File

@@ -2330,59 +2330,7 @@ export interface TelemetrytypesTelemetryFieldValuesDTO {
stringValues?: string[];
}
export interface TypesGettableGlobalConfigDTO {
/**
* @type string
*/
external_url?: string;
/**
* @type string
*/
ingestion_url?: string;
}
export interface TypesIdentifiableDTO {
/**
* @type string
*/
id: string;
}
export interface TypesOrganizationDTO {
/**
* @type string
*/
alias?: string;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
displayName?: string;
/**
* @type string
*/
id: string;
/**
* @type integer
* @minimum 0
*/
key?: number;
/**
* @type string
*/
name?: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
}
export interface UsertypesChangePasswordRequestDTO {
export interface TypesChangePasswordRequestDTO {
/**
* @type string
*/
@@ -2397,7 +2345,7 @@ export interface UsertypesChangePasswordRequestDTO {
userId?: string;
}
export interface UsertypesGettableAPIKeyDTO {
export interface TypesGettableAPIKeyDTO {
/**
* @type string
* @format date-time
@@ -2407,7 +2355,7 @@ export interface UsertypesGettableAPIKeyDTO {
* @type string
*/
createdBy?: string;
createdByUser?: UsertypesUserDTO;
createdByUser?: TypesUserDTO;
/**
* @type integer
* @format int64
@@ -2447,14 +2395,32 @@ export interface UsertypesGettableAPIKeyDTO {
* @type string
*/
updatedBy?: string;
updatedByUser?: UsertypesUserDTO;
updatedByUser?: TypesUserDTO;
/**
* @type string
*/
userId?: string;
}
export interface UsertypesInviteDTO {
export interface TypesGettableGlobalConfigDTO {
/**
* @type string
*/
external_url?: string;
/**
* @type string
*/
ingestion_url?: string;
}
export interface TypesIdentifiableDTO {
/**
* @type string
*/
id: string;
}
export interface TypesInviteDTO {
/**
* @type string
* @format date-time
@@ -2495,7 +2461,41 @@ export interface UsertypesInviteDTO {
updatedAt?: Date;
}
export interface UsertypesPostableAPIKeyDTO {
export interface TypesOrganizationDTO {
/**
* @type string
*/
alias?: string;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
displayName?: string;
/**
* @type string
*/
id: string;
/**
* @type integer
* @minimum 0
*/
key?: number;
/**
* @type string
*/
name?: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
}
export interface TypesPostableAPIKeyDTO {
/**
* @type integer
* @format int64
@@ -2511,33 +2511,14 @@ export interface UsertypesPostableAPIKeyDTO {
role?: string;
}
export interface UsertypesPostableAcceptInviteDTO {
/**
* @type string
*/
displayName?: string;
/**
* @type string
*/
password?: string;
/**
* @type string
*/
sourceUrl?: string;
/**
* @type string
*/
token?: string;
}
export interface UsertypesPostableBulkInviteRequestDTO {
export interface TypesPostableBulkInviteRequestDTO {
/**
* @type array
*/
invites: UsertypesPostableInviteDTO[];
invites: TypesPostableInviteDTO[];
}
export interface UsertypesPostableForgotPasswordDTO {
export interface TypesPostableForgotPasswordDTO {
/**
* @type string
*/
@@ -2552,7 +2533,7 @@ export interface UsertypesPostableForgotPasswordDTO {
orgId: string;
}
export interface UsertypesPostableInviteDTO {
export interface TypesPostableInviteDTO {
/**
* @type string
*/
@@ -2571,7 +2552,7 @@ export interface UsertypesPostableInviteDTO {
role?: string;
}
export interface UsertypesPostableResetPasswordDTO {
export interface TypesPostableResetPasswordDTO {
/**
* @type string
*/
@@ -2582,7 +2563,7 @@ export interface UsertypesPostableResetPasswordDTO {
token?: string;
}
export interface UsertypesResetPasswordTokenDTO {
export interface TypesResetPasswordTokenDTO {
/**
* @type string
* @format date-time
@@ -2602,7 +2583,7 @@ export interface UsertypesResetPasswordTokenDTO {
token?: string;
}
export interface UsertypesStorableAPIKeyDTO {
export interface TypesStorableAPIKeyDTO {
/**
* @type string
* @format date-time
@@ -2647,7 +2628,7 @@ export interface UsertypesStorableAPIKeyDTO {
userId?: string;
}
export interface UsertypesUserDTO {
export interface TypesUserDTO {
/**
* @type string
* @format date-time
@@ -3018,7 +2999,7 @@ export type GetResetPasswordTokenPathParameters = {
id: string;
};
export type GetResetPasswordToken200 = {
data: UsertypesResetPasswordTokenDTO;
data: TypesResetPasswordTokenDTO;
/**
* @type string
*/
@@ -3033,41 +3014,8 @@ export type GetGlobalConfig200 = {
status: string;
};
export type ListInvite200 = {
/**
* @type array
*/
data: UsertypesInviteDTO[];
/**
* @type string
*/
status: string;
};
export type CreateInvite201 = {
data: UsertypesInviteDTO;
/**
* @type string
*/
status: string;
};
export type DeleteInvitePathParameters = {
id: string;
};
export type GetInvitePathParameters = {
token: string;
};
export type GetInvite200 = {
data: UsertypesInviteDTO;
/**
* @type string
*/
status: string;
};
export type AcceptInvite201 = {
data: UsertypesUserDTO;
data: TypesInviteDTO;
/**
* @type string
*/
@@ -3115,7 +3063,7 @@ export type ListAPIKeys200 = {
/**
* @type array
*/
data: UsertypesGettableAPIKeyDTO[];
data: TypesGettableAPIKeyDTO[];
/**
* @type string
*/
@@ -3123,7 +3071,7 @@ export type ListAPIKeys200 = {
};
export type CreateAPIKey201 = {
data: UsertypesGettableAPIKeyDTO;
data: TypesGettableAPIKeyDTO;
/**
* @type string
*/
@@ -3290,7 +3238,7 @@ export type ListUsers200 = {
/**
* @type array
*/
data: UsertypesUserDTO[];
data: TypesUserDTO[];
/**
* @type string
*/
@@ -3304,7 +3252,7 @@ export type GetUserPathParameters = {
id: string;
};
export type GetUser200 = {
data: UsertypesUserDTO;
data: TypesUserDTO;
/**
* @type string
*/
@@ -3315,7 +3263,7 @@ export type UpdateUserPathParameters = {
id: string;
};
export type UpdateUser200 = {
data: UsertypesUserDTO;
data: TypesUserDTO;
/**
* @type string
*/
@@ -3323,7 +3271,7 @@ export type UpdateUser200 = {
};
export type GetMyUser200 = {
data: UsertypesUserDTO;
data: TypesUserDTO;
/**
* @type string
*/

View File

@@ -20,36 +20,30 @@ import { useMutation, useQuery } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
AcceptInvite201,
ChangePasswordPathParameters,
CreateAPIKey201,
CreateInvite201,
DeleteInvitePathParameters,
DeleteUserPathParameters,
GetInvite200,
GetInvitePathParameters,
GetMyUser200,
GetResetPasswordToken200,
GetResetPasswordTokenPathParameters,
GetUser200,
GetUserPathParameters,
ListAPIKeys200,
ListInvite200,
ListUsers200,
RenderErrorResponseDTO,
RevokeAPIKeyPathParameters,
TypesChangePasswordRequestDTO,
TypesPostableAPIKeyDTO,
TypesPostableBulkInviteRequestDTO,
TypesPostableForgotPasswordDTO,
TypesPostableInviteDTO,
TypesPostableResetPasswordDTO,
TypesStorableAPIKeyDTO,
TypesUserDTO,
UpdateAPIKeyPathParameters,
UpdateUser200,
UpdateUserPathParameters,
UsertypesChangePasswordRequestDTO,
UsertypesPostableAcceptInviteDTO,
UsertypesPostableAPIKeyDTO,
UsertypesPostableBulkInviteRequestDTO,
UsertypesPostableForgotPasswordDTO,
UsertypesPostableInviteDTO,
UsertypesPostableResetPasswordDTO,
UsertypesStorableAPIKeyDTO,
UsertypesUserDTO,
} from '../sigNoz.schemas';
/**
@@ -58,14 +52,14 @@ import type {
*/
export const changePassword = (
{ id }: ChangePasswordPathParameters,
usertypesChangePasswordRequestDTO: BodyType<UsertypesChangePasswordRequestDTO>,
typesChangePasswordRequestDTO: BodyType<TypesChangePasswordRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/changePassword/${id}`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: usertypesChangePasswordRequestDTO,
data: typesChangePasswordRequestDTO,
signal,
});
};
@@ -79,7 +73,7 @@ export const getChangePasswordMutationOptions = <
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<UsertypesChangePasswordRequestDTO>;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
>;
@@ -88,7 +82,7 @@ export const getChangePasswordMutationOptions = <
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<UsertypesChangePasswordRequestDTO>;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
> => {
@@ -105,7 +99,7 @@ export const getChangePasswordMutationOptions = <
Awaited<ReturnType<typeof changePassword>>,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<UsertypesChangePasswordRequestDTO>;
data: BodyType<TypesChangePasswordRequestDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -119,7 +113,7 @@ export const getChangePasswordMutationOptions = <
export type ChangePasswordMutationResult = NonNullable<
Awaited<ReturnType<typeof changePassword>>
>;
export type ChangePasswordMutationBody = BodyType<UsertypesChangePasswordRequestDTO>;
export type ChangePasswordMutationBody = BodyType<TypesChangePasswordRequestDTO>;
export type ChangePasswordMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -134,7 +128,7 @@ export const useChangePassword = <
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<UsertypesChangePasswordRequestDTO>;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
>;
@@ -143,7 +137,7 @@ export const useChangePassword = <
TError,
{
pathParams: ChangePasswordPathParameters;
data: BodyType<UsertypesChangePasswordRequestDTO>;
data: BodyType<TypesChangePasswordRequestDTO>;
},
TContext
> => {
@@ -255,97 +249,19 @@ export const invalidateGetResetPasswordToken = async (
return queryClient;
};
/**
* This endpoint lists all invites
* @summary List invites
*/
export const listInvite = (signal?: AbortSignal) => {
return GeneratedAPIInstance<ListInvite200>({
url: `/api/v1/invite`,
method: 'GET',
signal,
});
};
export const getListInviteQueryKey = () => {
return [`/api/v1/invite`] as const;
};
export const getListInviteQueryOptions = <
TData = Awaited<ReturnType<typeof listInvite>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof listInvite>>, TError, TData>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListInviteQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof listInvite>>> = ({
signal,
}) => listInvite(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listInvite>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListInviteQueryResult = NonNullable<
Awaited<ReturnType<typeof listInvite>>
>;
export type ListInviteQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List invites
*/
export function useListInvite<
TData = Awaited<ReturnType<typeof listInvite>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof listInvite>>, TError, TData>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListInviteQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List invites
*/
export const invalidateListInvite = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListInviteQueryKey() },
options,
);
return queryClient;
};
/**
* This endpoint creates an invite for a user
* @summary Create invite
*/
export const createInvite = (
usertypesPostableInviteDTO: BodyType<UsertypesPostableInviteDTO>,
typesPostableInviteDTO: BodyType<TypesPostableInviteDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateInvite201>({
url: `/api/v1/invite`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: usertypesPostableInviteDTO,
data: typesPostableInviteDTO,
signal,
});
};
@@ -357,13 +273,13 @@ export const getCreateInviteMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createInvite>>,
TError,
{ data: BodyType<UsertypesPostableInviteDTO> },
{ data: BodyType<TypesPostableInviteDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createInvite>>,
TError,
{ data: BodyType<UsertypesPostableInviteDTO> },
{ data: BodyType<TypesPostableInviteDTO> },
TContext
> => {
const mutationKey = ['createInvite'];
@@ -377,7 +293,7 @@ export const getCreateInviteMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createInvite>>,
{ data: BodyType<UsertypesPostableInviteDTO> }
{ data: BodyType<TypesPostableInviteDTO> }
> = (props) => {
const { data } = props ?? {};
@@ -390,7 +306,7 @@ export const getCreateInviteMutationOptions = <
export type CreateInviteMutationResult = NonNullable<
Awaited<ReturnType<typeof createInvite>>
>;
export type CreateInviteMutationBody = BodyType<UsertypesPostableInviteDTO>;
export type CreateInviteMutationBody = BodyType<TypesPostableInviteDTO>;
export type CreateInviteMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -403,283 +319,32 @@ export const useCreateInvite = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createInvite>>,
TError,
{ data: BodyType<UsertypesPostableInviteDTO> },
{ data: BodyType<TypesPostableInviteDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createInvite>>,
TError,
{ data: BodyType<UsertypesPostableInviteDTO> },
{ data: BodyType<TypesPostableInviteDTO> },
TContext
> => {
const mutationOptions = getCreateInviteMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint deletes an invite by id
* @summary Delete invite
*/
export const deleteInvite = ({ id }: DeleteInvitePathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/invite/${id}`,
method: 'DELETE',
});
};
export const getDeleteInviteMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteInvite>>,
TError,
{ pathParams: DeleteInvitePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteInvite>>,
TError,
{ pathParams: DeleteInvitePathParameters },
TContext
> => {
const mutationKey = ['deleteInvite'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteInvite>>,
{ pathParams: DeleteInvitePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteInvite(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteInviteMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteInvite>>
>;
export type DeleteInviteMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete invite
*/
export const useDeleteInvite = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteInvite>>,
TError,
{ pathParams: DeleteInvitePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteInvite>>,
TError,
{ pathParams: DeleteInvitePathParameters },
TContext
> => {
const mutationOptions = getDeleteInviteMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint gets an invite by token
* @summary Get invite
*/
export const getInvite = (
{ token }: GetInvitePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetInvite200>({
url: `/api/v1/invite/${token}`,
method: 'GET',
signal,
});
};
export const getGetInviteQueryKey = ({ token }: GetInvitePathParameters) => {
return [`/api/v1/invite/${token}`] as const;
};
export const getGetInviteQueryOptions = <
TData = Awaited<ReturnType<typeof getInvite>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ token }: GetInvitePathParameters,
options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof getInvite>>, TError, TData>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetInviteQueryKey({ token });
const queryFn: QueryFunction<Awaited<ReturnType<typeof getInvite>>> = ({
signal,
}) => getInvite({ token }, signal);
return {
queryKey,
queryFn,
enabled: !!token,
...queryOptions,
} as UseQueryOptions<Awaited<ReturnType<typeof getInvite>>, TError, TData> & {
queryKey: QueryKey;
};
};
export type GetInviteQueryResult = NonNullable<
Awaited<ReturnType<typeof getInvite>>
>;
export type GetInviteQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get invite
*/
export function useGetInvite<
TData = Awaited<ReturnType<typeof getInvite>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ token }: GetInvitePathParameters,
options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof getInvite>>, TError, TData>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetInviteQueryOptions({ token }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get invite
*/
export const invalidateGetInvite = async (
queryClient: QueryClient,
{ token }: GetInvitePathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetInviteQueryKey({ token }) },
options,
);
return queryClient;
};
/**
* This endpoint accepts an invite by token
* @summary Accept invite
*/
export const acceptInvite = (
usertypesPostableAcceptInviteDTO: BodyType<UsertypesPostableAcceptInviteDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<AcceptInvite201>({
url: `/api/v1/invite/accept`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: usertypesPostableAcceptInviteDTO,
signal,
});
};
export const getAcceptInviteMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof acceptInvite>>,
TError,
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof acceptInvite>>,
TError,
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
TContext
> => {
const mutationKey = ['acceptInvite'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof acceptInvite>>,
{ data: BodyType<UsertypesPostableAcceptInviteDTO> }
> = (props) => {
const { data } = props ?? {};
return acceptInvite(data);
};
return { mutationFn, ...mutationOptions };
};
export type AcceptInviteMutationResult = NonNullable<
Awaited<ReturnType<typeof acceptInvite>>
>;
export type AcceptInviteMutationBody = BodyType<UsertypesPostableAcceptInviteDTO>;
export type AcceptInviteMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Accept invite
*/
export const useAcceptInvite = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof acceptInvite>>,
TError,
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof acceptInvite>>,
TError,
{ data: BodyType<UsertypesPostableAcceptInviteDTO> },
TContext
> => {
const mutationOptions = getAcceptInviteMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint creates a bulk invite for a user
* @summary Create bulk invite
*/
export const createBulkInvite = (
usertypesPostableBulkInviteRequestDTO: BodyType<UsertypesPostableBulkInviteRequestDTO>,
typesPostableBulkInviteRequestDTO: BodyType<TypesPostableBulkInviteRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/invite/bulk`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: usertypesPostableBulkInviteRequestDTO,
data: typesPostableBulkInviteRequestDTO,
signal,
});
};
@@ -691,13 +356,13 @@ export const getCreateBulkInviteMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createBulkInvite>>,
TError,
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createBulkInvite>>,
TError,
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
TContext
> => {
const mutationKey = ['createBulkInvite'];
@@ -711,7 +376,7 @@ export const getCreateBulkInviteMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createBulkInvite>>,
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> }
{ data: BodyType<TypesPostableBulkInviteRequestDTO> }
> = (props) => {
const { data } = props ?? {};
@@ -724,7 +389,7 @@ export const getCreateBulkInviteMutationOptions = <
export type CreateBulkInviteMutationResult = NonNullable<
Awaited<ReturnType<typeof createBulkInvite>>
>;
export type CreateBulkInviteMutationBody = BodyType<UsertypesPostableBulkInviteRequestDTO>;
export type CreateBulkInviteMutationBody = BodyType<TypesPostableBulkInviteRequestDTO>;
export type CreateBulkInviteMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -737,13 +402,13 @@ export const useCreateBulkInvite = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createBulkInvite>>,
TError,
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createBulkInvite>>,
TError,
{ data: BodyType<UsertypesPostableBulkInviteRequestDTO> },
{ data: BodyType<TypesPostableBulkInviteRequestDTO> },
TContext
> => {
const mutationOptions = getCreateBulkInviteMutationOptions(options);
@@ -841,14 +506,14 @@ export const invalidateListAPIKeys = async (
* @summary Create api key
*/
export const createAPIKey = (
usertypesPostableAPIKeyDTO: BodyType<UsertypesPostableAPIKeyDTO>,
typesPostableAPIKeyDTO: BodyType<TypesPostableAPIKeyDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateAPIKey201>({
url: `/api/v1/pats`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: usertypesPostableAPIKeyDTO,
data: typesPostableAPIKeyDTO,
signal,
});
};
@@ -860,13 +525,13 @@ export const getCreateAPIKeyMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
> => {
const mutationKey = ['createAPIKey'];
@@ -880,7 +545,7 @@ export const getCreateAPIKeyMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createAPIKey>>,
{ data: BodyType<UsertypesPostableAPIKeyDTO> }
{ data: BodyType<TypesPostableAPIKeyDTO> }
> = (props) => {
const { data } = props ?? {};
@@ -893,7 +558,7 @@ export const getCreateAPIKeyMutationOptions = <
export type CreateAPIKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof createAPIKey>>
>;
export type CreateAPIKeyMutationBody = BodyType<UsertypesPostableAPIKeyDTO>;
export type CreateAPIKeyMutationBody = BodyType<TypesPostableAPIKeyDTO>;
export type CreateAPIKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -906,13 +571,13 @@ export const useCreateAPIKey = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<UsertypesPostableAPIKeyDTO> },
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
> => {
const mutationOptions = getCreateAPIKeyMutationOptions(options);
@@ -1002,13 +667,13 @@ export const useRevokeAPIKey = <
*/
export const updateAPIKey = (
{ id }: UpdateAPIKeyPathParameters,
usertypesStorableAPIKeyDTO: BodyType<UsertypesStorableAPIKeyDTO>,
typesStorableAPIKeyDTO: BodyType<TypesStorableAPIKeyDTO>,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/pats/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: usertypesStorableAPIKeyDTO,
data: typesStorableAPIKeyDTO,
});
};
@@ -1021,7 +686,7 @@ export const getUpdateAPIKeyMutationOptions = <
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<UsertypesStorableAPIKeyDTO>;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
>;
@@ -1030,7 +695,7 @@ export const getUpdateAPIKeyMutationOptions = <
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<UsertypesStorableAPIKeyDTO>;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
> => {
@@ -1047,7 +712,7 @@ export const getUpdateAPIKeyMutationOptions = <
Awaited<ReturnType<typeof updateAPIKey>>,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<UsertypesStorableAPIKeyDTO>;
data: BodyType<TypesStorableAPIKeyDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -1061,7 +726,7 @@ export const getUpdateAPIKeyMutationOptions = <
export type UpdateAPIKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof updateAPIKey>>
>;
export type UpdateAPIKeyMutationBody = BodyType<UsertypesStorableAPIKeyDTO>;
export type UpdateAPIKeyMutationBody = BodyType<TypesStorableAPIKeyDTO>;
export type UpdateAPIKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -1076,7 +741,7 @@ export const useUpdateAPIKey = <
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<UsertypesStorableAPIKeyDTO>;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
>;
@@ -1085,7 +750,7 @@ export const useUpdateAPIKey = <
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<UsertypesStorableAPIKeyDTO>;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
> => {
@@ -1098,14 +763,14 @@ export const useUpdateAPIKey = <
* @summary Reset password
*/
export const resetPassword = (
usertypesPostableResetPasswordDTO: BodyType<UsertypesPostableResetPasswordDTO>,
typesPostableResetPasswordDTO: BodyType<TypesPostableResetPasswordDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/resetPassword`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: usertypesPostableResetPasswordDTO,
data: typesPostableResetPasswordDTO,
signal,
});
};
@@ -1117,13 +782,13 @@ export const getResetPasswordMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof resetPassword>>,
TError,
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
{ data: BodyType<TypesPostableResetPasswordDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof resetPassword>>,
TError,
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
{ data: BodyType<TypesPostableResetPasswordDTO> },
TContext
> => {
const mutationKey = ['resetPassword'];
@@ -1137,7 +802,7 @@ export const getResetPasswordMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof resetPassword>>,
{ data: BodyType<UsertypesPostableResetPasswordDTO> }
{ data: BodyType<TypesPostableResetPasswordDTO> }
> = (props) => {
const { data } = props ?? {};
@@ -1150,7 +815,7 @@ export const getResetPasswordMutationOptions = <
export type ResetPasswordMutationResult = NonNullable<
Awaited<ReturnType<typeof resetPassword>>
>;
export type ResetPasswordMutationBody = BodyType<UsertypesPostableResetPasswordDTO>;
export type ResetPasswordMutationBody = BodyType<TypesPostableResetPasswordDTO>;
export type ResetPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -1163,13 +828,13 @@ export const useResetPassword = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof resetPassword>>,
TError,
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
{ data: BodyType<TypesPostableResetPasswordDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof resetPassword>>,
TError,
{ data: BodyType<UsertypesPostableResetPasswordDTO> },
{ data: BodyType<TypesPostableResetPasswordDTO> },
TContext
> => {
const mutationOptions = getResetPasswordMutationOptions(options);
@@ -1428,13 +1093,13 @@ export const invalidateGetUser = async (
*/
export const updateUser = (
{ id }: UpdateUserPathParameters,
usertypesUserDTO: BodyType<UsertypesUserDTO>,
typesUserDTO: BodyType<TypesUserDTO>,
) => {
return GeneratedAPIInstance<UpdateUser200>({
url: `/api/v1/user/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: usertypesUserDTO,
data: typesUserDTO,
});
};
@@ -1445,13 +1110,13 @@ export const getUpdateUserMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateUser>>,
TError,
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateUser>>,
TError,
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
TContext
> => {
const mutationKey = ['updateUser'];
@@ -1465,7 +1130,7 @@ export const getUpdateUserMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateUser>>,
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> }
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> }
> = (props) => {
const { pathParams, data } = props ?? {};
@@ -1478,7 +1143,7 @@ export const getUpdateUserMutationOptions = <
export type UpdateUserMutationResult = NonNullable<
Awaited<ReturnType<typeof updateUser>>
>;
export type UpdateUserMutationBody = BodyType<UsertypesUserDTO>;
export type UpdateUserMutationBody = BodyType<TypesUserDTO>;
export type UpdateUserMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -1491,13 +1156,13 @@ export const useUpdateUser = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateUser>>,
TError,
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateUser>>,
TError,
{ pathParams: UpdateUserPathParameters; data: BodyType<UsertypesUserDTO> },
{ pathParams: UpdateUserPathParameters; data: BodyType<TypesUserDTO> },
TContext
> => {
const mutationOptions = getUpdateUserMutationOptions(options);
@@ -1587,14 +1252,14 @@ export const invalidateGetMyUser = async (
* @summary Forgot password
*/
export const forgotPassword = (
usertypesPostableForgotPasswordDTO: BodyType<UsertypesPostableForgotPasswordDTO>,
typesPostableForgotPasswordDTO: BodyType<TypesPostableForgotPasswordDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/factor_password/forgot`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: usertypesPostableForgotPasswordDTO,
data: typesPostableForgotPasswordDTO,
signal,
});
};
@@ -1606,13 +1271,13 @@ export const getForgotPasswordMutationOptions = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof forgotPassword>>,
TError,
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
{ data: BodyType<TypesPostableForgotPasswordDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof forgotPassword>>,
TError,
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
{ data: BodyType<TypesPostableForgotPasswordDTO> },
TContext
> => {
const mutationKey = ['forgotPassword'];
@@ -1626,7 +1291,7 @@ export const getForgotPasswordMutationOptions = <
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof forgotPassword>>,
{ data: BodyType<UsertypesPostableForgotPasswordDTO> }
{ data: BodyType<TypesPostableForgotPasswordDTO> }
> = (props) => {
const { data } = props ?? {};
@@ -1639,7 +1304,7 @@ export const getForgotPasswordMutationOptions = <
export type ForgotPasswordMutationResult = NonNullable<
Awaited<ReturnType<typeof forgotPassword>>
>;
export type ForgotPasswordMutationBody = BodyType<UsertypesPostableForgotPasswordDTO>;
export type ForgotPasswordMutationBody = BodyType<TypesPostableForgotPasswordDTO>;
export type ForgotPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
/**
@@ -1652,13 +1317,13 @@ export const useForgotPassword = <
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof forgotPassword>>,
TError,
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
{ data: BodyType<TypesPostableForgotPasswordDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof forgotPassword>>,
TError,
{ data: BodyType<UsertypesPostableForgotPasswordDTO> },
{ data: BodyType<TypesPostableForgotPasswordDTO> },
TContext
> => {
const mutationOptions = getForgotPasswordMutationOptions(options);

View File

@@ -1,19 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, PendingInvite } from 'types/api/user/getPendingInvites';
const get = async (): Promise<SuccessResponseV2<PendingInvite[]>> => {
try {
const response = await axios.get<PayloadProps>(`/invite`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default get;

View File

@@ -1,22 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/accept';
import { UserResponse } from 'types/api/user/getUser';
const accept = async (
props: Props,
): Promise<SuccessResponseV2<UserResponse>> => {
try {
const response = await axios.post<PayloadProps>(`/invite/accept`, props);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default accept;

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Props } from 'types/api/user/deleteInvite';
const del = async (props: Props): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.delete(`/invite/${props.id}`);
return {
httpStatusCode: response.status,
data: null,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default del;

View File

@@ -1,28 +0,0 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
InviteDetails,
PayloadProps,
Props,
} from 'types/api/user/getInviteDetails';
const getInviteDetails = async (
props: Props,
): Promise<SuccessResponseV2<InviteDetails>> => {
try {
const response = await axios.get<PayloadProps>(
`/invite/${props.inviteId}?ref=${window.location.href}`,
);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getInviteDetails;

View File

@@ -30,3 +30,4 @@ import '@signozhq/switch';
import '@signozhq/table';
import '@signozhq/toggle-group';
import '@signozhq/tooltip';
import '@signozhq/ui';

View File

@@ -7,7 +7,6 @@ import {
Check,
ChevronDown,
Copy,
Link,
LockKeyhole,
RefreshCw,
Trash2,
@@ -17,14 +16,11 @@ import { Input } from '@signozhq/input';
import { toast } from '@signozhq/sonner';
import { Select } from 'antd';
import getResetPasswordToken from 'api/v1/factor_password/getResetPasswordToken';
import sendInvite from 'api/v1/invite/create';
import cancelInvite from 'api/v1/invite/id/delete';
import deleteUser from 'api/v1/user/id/delete';
import update from 'api/v1/user/id/update';
import { MemberRow } from 'components/MembersTable/MembersTable';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import ROUTES from 'constants/routes';
import { INVITE_PREFIX, MemberStatus } from 'container/MembersSettings/utils';
import { MemberStatus } from 'container/MembersSettings/utils';
import { capitalize } from 'lodash-es';
import { useTimezone } from 'providers/Timezone';
import { ROLES } from 'types/roles';
@@ -36,7 +32,6 @@ export interface EditMemberDrawerProps {
open: boolean;
onClose: () => void;
onComplete: () => void;
onRefetch?: () => void;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
@@ -45,7 +40,6 @@ function EditMemberDrawer({
open,
onClose,
onComplete,
onRefetch,
}: EditMemberDrawerProps): JSX.Element {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
@@ -58,11 +52,9 @@ function EditMemberDrawer({
const [resetLink, setResetLink] = useState<string | null>(null);
const [showResetLinkDialog, setShowResetLinkDialog] = useState(false);
const [hasCopiedResetLink, setHasCopiedResetLink] = useState(false);
const [linkType, setLinkType] = useState<'invite' | 'reset' | null>(null);
const isInvited = member?.status === MemberStatus.Invited;
// Invited member IDs are prefixed with 'invite-'; strip it to get the real invite ID
const inviteId =
isInvited && member ? member.id.slice(INVITE_PREFIX.length) : null;
useEffect(() => {
if (member) {
@@ -73,7 +65,7 @@ function EditMemberDrawer({
const isDirty =
member !== null &&
(displayName !== member.name || selectedRole !== member.role);
(displayName !== (member.name ?? '') || selectedRole !== member.role);
const formatTimestamp = useCallback(
(ts: string | null | undefined): string => {
@@ -89,80 +81,22 @@ function EditMemberDrawer({
[formatTimezoneAdjustedTimestamp],
);
const saveInvitedMember = useCallback(async (): Promise<void> => {
if (!member || !inviteId) {
return;
}
await cancelInvite({ id: inviteId });
try {
await sendInvite({
email: member.email,
name: displayName,
role: selectedRole,
frontendBaseUrl: window.location.origin,
});
toast.success('Invite updated successfully', { richColors: true });
onComplete();
onClose();
} catch {
onRefetch?.();
onClose();
toast.error(
'Failed to send the updated invite. Please re-invite this member.',
{ richColors: true },
);
}
}, [
member,
inviteId,
displayName,
selectedRole,
onComplete,
onClose,
onRefetch,
]);
const saveActiveMember = useCallback(async (): Promise<void> => {
if (!member) {
return;
}
await update({
userId: member.id,
displayName,
role: selectedRole,
});
toast.success('Member details updated successfully', { richColors: true });
onComplete();
onClose();
}, [member, displayName, selectedRole, onComplete, onClose]);
const handleSave = useCallback(async (): Promise<void> => {
if (!member || !isDirty) {
return;
}
setIsSaving(true);
try {
if (isInvited && inviteId) {
await saveInvitedMember();
} else {
await saveActiveMember();
}
await update({ userId: member.id, displayName, role: selectedRole });
toast.success('Member details updated successfully', { richColors: true });
onComplete();
onClose();
} catch {
toast.error(
isInvited ? 'Failed to update invite' : 'Failed to update member details',
{ richColors: true },
);
toast.error('Failed to update member details', { richColors: true });
} finally {
setIsSaving(false);
}
}, [
member,
isDirty,
isInvited,
inviteId,
saveInvitedMember,
saveActiveMember,
]);
}, [member, isDirty, displayName, selectedRole, onComplete, onClose]);
const handleDelete = useCallback(async (): Promise<void> => {
if (!member) {
@@ -170,25 +104,23 @@ function EditMemberDrawer({
}
setIsDeleting(true);
try {
if (isInvited && inviteId) {
await cancelInvite({ id: inviteId });
toast.success('Invitation cancelled successfully', { richColors: true });
} else {
await deleteUser({ userId: member.id });
toast.success('Member deleted successfully', { richColors: true });
}
await deleteUser({ userId: member.id });
toast.success(
isInvited ? 'Invite revoked successfully' : 'Member deleted successfully',
{ richColors: true },
);
setShowDeleteConfirm(false);
onComplete();
onClose();
} catch {
toast.error(
isInvited ? 'Failed to cancel invitation' : 'Failed to delete member',
isInvited ? 'Failed to revoke invite' : 'Failed to delete member',
{ richColors: true },
);
} finally {
setIsDeleting(false);
}
}, [member, isInvited, inviteId, onComplete, onClose]);
}, [member, isInvited, onComplete, onClose]);
const handleGenerateResetLink = useCallback(async (): Promise<void> => {
if (!member) {
@@ -201,6 +133,7 @@ function EditMemberDrawer({
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
setResetLink(link);
setHasCopiedResetLink(false);
setLinkType(isInvited ? 'invite' : 'reset');
setShowResetLinkDialog(true);
onClose();
} else {
@@ -217,7 +150,7 @@ function EditMemberDrawer({
} finally {
setIsGeneratingLink(false);
}
}, [member, onClose]);
}, [member, isInvited, setLinkType, onClose]);
const handleCopyResetLink = useCallback(async (): Promise<void> => {
if (!resetLink) {
@@ -227,36 +160,18 @@ function EditMemberDrawer({
await navigator.clipboard.writeText(resetLink);
setHasCopiedResetLink(true);
setTimeout(() => setHasCopiedResetLink(false), 2000);
toast.success('Reset link copied to clipboard', { richColors: true });
toast.success(
linkType === 'invite'
? 'Invite link copied to clipboard'
: 'Reset link copied to clipboard',
{ richColors: true },
);
} catch {
toast.error('Failed to copy link', {
richColors: true,
});
}
}, [resetLink]);
const handleCopyInviteLink = useCallback(async (): Promise<void> => {
if (!member?.token) {
toast.error('Invite link is not available', {
richColors: true,
position: 'top-right',
});
return;
}
const inviteLink = `${window.location.origin}${ROUTES.SIGN_UP}?token=${member.token}`;
try {
await navigator.clipboard.writeText(inviteLink);
toast.success('Invite link copied to clipboard', {
richColors: true,
position: 'top-right',
});
} catch {
toast.error('Failed to copy invite link', {
richColors: true,
position: 'top-right',
});
}
}, [member]);
}, [resetLink, linkType]);
const handleClose = useCallback((): void => {
setShowDeleteConfirm(false);
@@ -348,30 +263,22 @@ function EditMemberDrawer({
onClick={(): void => setShowDeleteConfirm(true)}
>
<Trash2 size={12} />
{isInvited ? 'Cancel Invite' : 'Delete Member'}
{isInvited ? 'Revoke Invite' : 'Delete Member'}
</Button>
<div className="edit-member-drawer__footer-divider" />
{isInvited ? (
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
onClick={handleCopyInviteLink}
disabled={!member?.token}
>
<Link size={12} />
Copy Invite Link
</Button>
) : (
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
onClick={handleGenerateResetLink}
disabled={isGeneratingLink}
>
<RefreshCw size={12} />
{isGeneratingLink ? 'Generating...' : 'Generate Password Reset Link'}
</Button>
)}
<Button
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
onClick={handleGenerateResetLink}
disabled={isGeneratingLink}
>
<RefreshCw size={12} />
{isGeneratingLink
? 'Generating...'
: isInvited
? 'Copy Invite Link'
: 'Generate Password Reset Link'}
</Button>
</div>
<div className="edit-member-drawer__footer-right">
@@ -394,21 +301,21 @@ function EditMemberDrawer({
</div>
);
const deleteDialogTitle = isInvited ? 'Cancel Invitation' : 'Delete Member';
const deleteDialogTitle = isInvited ? 'Revoke Invite' : 'Delete Member';
const deleteDialogBody = isInvited ? (
<>
Are you sure you want to cancel the invitation for{' '}
Are you sure you want to revoke the invite for{' '}
<strong>{member?.email}</strong>? They will no longer be able to join the
workspace using this invite.
</>
) : (
<>
Are you sure you want to delete{' '}
<strong>{member?.name || member?.email}</strong>? This will permanently
remove their access to the workspace.
<strong>{member?.name || member?.email}</strong>? This will remove their
access to the workspace.
</>
);
const deleteConfirmLabel = isInvited ? 'Cancel Invite' : 'Delete Member';
const deleteConfirmLabel = isInvited ? 'Revoke Invite' : 'Delete Member';
return (
<>
@@ -434,17 +341,19 @@ function EditMemberDrawer({
onOpenChange={(isOpen): void => {
if (!isOpen) {
setShowResetLinkDialog(false);
setLinkType(null);
}
}}
title="Password Reset Link"
title={linkType === 'invite' ? 'Invite Link' : 'Password Reset Link'}
showCloseButton
width="base"
className="reset-link-dialog"
>
<div className="reset-link-dialog__content">
<p className="reset-link-dialog__description">
This creates a one-time link the team member can use to set a new password
for their SigNoz account.
{linkType === 'invite'
? 'Share this one-time link with the team member to complete their account setup.'
: 'This creates a one-time link the team member can use to set a new password for their SigNoz account.'}
</p>
<div className="reset-link-dialog__link-row">
<div className="reset-link-dialog__link-text-wrap">

View File

@@ -1,7 +1,6 @@
import type { ReactNode } from 'react';
import { toast } from '@signozhq/sonner';
import getResetPasswordToken from 'api/v1/factor_password/getResetPasswordToken';
import cancelInvite from 'api/v1/invite/id/delete';
import deleteUser from 'api/v1/user/id/delete';
import update from 'api/v1/user/id/update';
import { MemberStatus } from 'container/MembersSettings/utils';
@@ -48,8 +47,6 @@ jest.mock('@signozhq/dialog', () => ({
jest.mock('api/v1/user/id/update');
jest.mock('api/v1/user/id/delete');
jest.mock('api/v1/invite/id/delete');
jest.mock('api/v1/invite/create');
jest.mock('api/v1/factor_password/getResetPasswordToken');
jest.mock('@signozhq/sonner', () => ({
toast: {
@@ -60,7 +57,6 @@ jest.mock('@signozhq/sonner', () => ({
const mockUpdate = jest.mocked(update);
const mockDeleteUser = jest.mocked(deleteUser);
const mockCancelInvite = jest.mocked(cancelInvite);
const mockGetResetPasswordToken = jest.mocked(getResetPasswordToken);
const activeMember = {
@@ -74,13 +70,12 @@ const activeMember = {
};
const invitedMember = {
id: 'invite-abc123',
id: 'abc123',
name: '',
email: 'bob@signoz.io',
role: 'VIEWER' as ROLES,
status: MemberStatus.Invited,
joinedOn: '1700000000000',
token: 'tok-xyz',
};
function renderDrawer(
@@ -102,7 +97,6 @@ describe('EditMemberDrawer', () => {
jest.clearAllMocks();
mockUpdate.mockResolvedValue({ httpStatusCode: 200, data: null });
mockDeleteUser.mockResolvedValue({ httpStatusCode: 200, data: null });
mockCancelInvite.mockResolvedValue({ httpStatusCode: 200, data: null });
});
it('renders active member details and disables Save when form is not dirty', () => {
@@ -163,36 +157,61 @@ describe('EditMemberDrawer', () => {
});
});
it('shows Cancel Invite and Copy Invite Link for invited members; hides Last Modified', () => {
it('shows revoke invite and copy invite link for invited members; hides Last Modified', () => {
renderDrawer({ member: invitedMember });
expect(
screen.getByRole('button', { name: /cancel invite/i }),
screen.getByRole('button', { name: /revoke invite/i }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /copy invite link/i }),
).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: /generate password reset link/i }),
).not.toBeInTheDocument();
expect(screen.getByText('Invited On')).toBeInTheDocument();
expect(screen.queryByText('Last Modified')).not.toBeInTheDocument();
});
it('calls cancelInvite after confirming Cancel Invite for invited members', async () => {
it('calls deleteUser after confirming revoke invite for invited members', async () => {
const onComplete = jest.fn();
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderDrawer({ member: invitedMember, onComplete });
await user.click(screen.getByRole('button', { name: /cancel invite/i }));
await user.click(screen.getByRole('button', { name: /revoke invite/i }));
expect(
await screen.findByText(/are you sure you want to cancel the invitation/i),
await screen.findByText(/Are you sure you want to revoke the invite/i),
).toBeInTheDocument();
const confirmBtns = screen.getAllByRole('button', { name: /cancel invite/i });
const confirmBtns = screen.getAllByRole('button', { name: /revoke invite/i });
await user.click(confirmBtns[confirmBtns.length - 1]);
await waitFor(() => {
expect(mockCancelInvite).toHaveBeenCalledWith({ id: 'abc123' });
expect(mockDeleteUser).toHaveBeenCalledWith({ userId: 'abc123' });
expect(onComplete).toHaveBeenCalled();
});
});
it('calls update API when saving changes for an invited member', async () => {
const onComplete = jest.fn();
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderDrawer({ member: { ...invitedMember, name: 'Bob' }, onComplete });
const nameInput = screen.getByDisplayValue('Bob');
await user.clear(nameInput);
await user.type(nameInput, 'Bob Updated');
const saveBtn = screen.getByRole('button', { name: /save member details/i });
await waitFor(() => expect(saveBtn).not.toBeDisabled());
await user.click(saveBtn);
await waitFor(() => {
expect(mockUpdate).toHaveBeenCalledWith(
expect.objectContaining({ userId: 'abc123', displayName: 'Bob Updated' }),
);
expect(onComplete).toHaveBeenCalled();
});
});
@@ -260,7 +279,6 @@ describe('EditMemberDrawer', () => {
fireEvent.click(screen.getByRole('button', { name: /^copy$/i }));
// Verify success path: writeText called with the correct link
await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith(
'Reset link copied to clipboard',

View File

@@ -1,6 +1,6 @@
import type React from 'react';
import { Badge } from '@signozhq/badge';
import { Pagination, Table, Tooltip } from 'antd';
import { Table, Tooltip } from 'antd';
import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { MemberStatus } from 'container/MembersSettings/utils';
@@ -18,7 +18,6 @@ export interface MemberRow {
status: MemberStatus;
joinedOn: string | null;
updatedAt?: string | null;
token?: string | null;
}
interface MembersTableProps {
@@ -64,11 +63,23 @@ function StatusBadge({ status }: { status: MemberRow['status'] }): JSX.Element {
</Badge>
);
}
return (
<Badge color="amber" variant="outline">
INVITED
</Badge>
);
if (status === MemberStatus.Deleted) {
return (
<Badge color="cherry" variant="outline">
DELETED
</Badge>
);
}
if (status === MemberStatus.Invited) {
return (
<Badge color="amber" variant="outline">
INVITED
</Badge>
);
}
return <Badge color="vanilla"></Badge>;
}
function MembersEmptyState({
@@ -199,14 +210,30 @@ function MembersTable({
dataSource={data}
rowKey="id"
loading={loading}
pagination={false}
pagination={{
current: currentPage,
pageSize,
total,
showTotal: showPaginationTotal,
showSizeChanger: false,
onChange: onPageChange,
className: 'members-table-pagination',
hideOnSinglePage: true,
}}
rowClassName={(_, index): string =>
index % 2 === 0 ? 'members-table-row--tinted' : ''
}
onRow={(record): React.HTMLAttributes<HTMLElement> => ({
onClick: (): void => onRowClick?.(record),
style: onRowClick ? { cursor: 'pointer' } : undefined,
})}
onRow={(record): React.HTMLAttributes<HTMLElement> => {
const isClickable = onRowClick && record.status !== MemberStatus.Deleted;
return {
onClick: (): void => {
if (isClickable) {
onRowClick(record);
}
},
style: isClickable ? { cursor: 'pointer' } : undefined,
};
}}
onChange={(_, __, sorter): void => {
if (onSortChange) {
onSortChange(
@@ -220,17 +247,6 @@ function MembersTable({
}}
className="members-table"
/>
{total > pageSize && (
<Pagination
current={currentPage}
pageSize={pageSize}
total={total}
showTotal={showPaginationTotal}
showSizeChanger={false}
onChange={onPageChange}
className="members-table-pagination"
/>
)}
</div>
);
}

View File

@@ -24,13 +24,12 @@ const mockActiveMembers: MemberRow[] = [
];
const mockInvitedMember: MemberRow = {
id: 'invite-abc',
id: 'inv-abc',
name: '',
email: 'charlie@signoz.io',
role: 'EDITOR' as ROLES,
status: MemberStatus.Invited,
joinedOn: null,
token: 'tok-123',
};
const defaultProps = {
@@ -93,6 +92,34 @@ describe('MembersTable', () => {
);
});
it('renders DELETED badge and does not call onRowClick when a deleted member row is clicked', async () => {
const onRowClick = jest.fn();
const user = userEvent.setup({ pointerEventsCheck: 0 });
const deletedMember: MemberRow = {
id: 'user-del',
name: 'Dave Deleted',
email: 'dave@signoz.io',
role: 'VIEWER' as ROLES,
status: MemberStatus.Deleted,
joinedOn: null,
};
render(
<MembersTable
{...defaultProps}
data={[...mockActiveMembers, deletedMember]}
total={3}
onRowClick={onRowClick}
/>,
);
expect(screen.getByText('DELETED')).toBeInTheDocument();
await user.click(screen.getByText('Dave Deleted'));
expect(onRowClick).not.toHaveBeenCalledWith(
expect.objectContaining({ id: 'user-del' }),
);
});
it('shows "No members found" empty state when no data and no search query', () => {
render(<MembersTable {...defaultProps} data={[]} total={0} searchQuery="" />);

View File

@@ -3,16 +3,14 @@ import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Button, Card, Skeleton, Typography } from 'antd';
import cx from 'classnames';
import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries';
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
import Uplot from 'components/Uplot';
import { PANEL_TYPES } from 'constants/queryBuilder';
import {
getCustomFiltersForBarChart,
getFormattedEndPointStatusCodeChartData,
getStatusCodeBarChartWidgetData,
statusCodeWidgetInfo,
} from 'container/ApiMonitoring/utils';
import BarChart from 'container/DashboardContainer/visualization/charts/BarChart/BarChart';
import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils';
import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton';
import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages';
@@ -20,15 +18,16 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { useNotifications } from 'hooks/useNotifications';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { LegendPosition } from 'lib/uPlotV2/components/types';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useTimezone } from 'providers/Timezone';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { Options } from 'uplot';
import ErrorState from './ErrorState';
import { prepareStatusCodeBarChartsConfig } from './utils';
function StatusCodeBarCharts({
endPointStatusCodeBarChartsDataQuery,
@@ -67,13 +66,6 @@ function StatusCodeBarCharts({
} = endPointStatusCodeLatencyBarChartsDataQuery;
const { startTime: minTime, endTime: maxTime } = timeRange;
const legendScrollPositionRef = useRef<{
scrollTop: number;
scrollLeft: number;
}>({
scrollTop: 0,
scrollLeft: 0,
});
const graphRef = useRef<HTMLDivElement>(null);
const dimensions = useResizeObserver(graphRef);
@@ -119,6 +111,7 @@ function StatusCodeBarCharts({
const navigateToExplorer = useNavigateToExplorer();
const { currentQuery } = useQueryBuilder();
const { timezone } = useTimezone();
const navigateToExplorerPages = useNavigateToExplorerPages();
const { notifications } = useNotifications();
@@ -134,12 +127,6 @@ function StatusCodeBarCharts({
[],
);
const { getCustomSeries } = useGetGraphCustomSeries({
isDarkMode,
drawStyle: 'bars',
colorMapping,
});
const widget = useMemo<Widgets>(
() =>
getStatusCodeBarChartWidgetData(domainName, {
@@ -193,49 +180,36 @@ function StatusCodeBarCharts({
],
);
const options = useMemo(
() =>
getUPlotChartOptions({
apiResponse:
currentWidgetInfoIndex === 0
? formattedEndPointStatusCodeBarChartsDataPayload
: formattedEndPointStatusCodeLatencyBarChartsDataPayload,
isDarkMode,
dimensions,
yAxisUnit: statusCodeWidgetInfo[currentWidgetInfoIndex].yAxisUnit,
softMax: null,
softMin: null,
minTimeScale: minTime,
maxTimeScale: maxTime,
panelType: PANEL_TYPES.BAR,
onClickHandler: graphClickHandler,
customSeries: getCustomSeries,
onDragSelect,
colorMapping,
query: currentQuery,
legendScrollPosition: legendScrollPositionRef.current,
setLegendScrollPosition: (position: {
scrollTop: number;
scrollLeft: number;
}) => {
legendScrollPositionRef.current = position;
},
}),
[
minTime,
maxTime,
currentWidgetInfoIndex,
dimensions,
formattedEndPointStatusCodeBarChartsDataPayload,
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
const config = useMemo(() => {
const apiResponse =
currentWidgetInfoIndex === 0
? formattedEndPointStatusCodeBarChartsDataPayload
: formattedEndPointStatusCodeLatencyBarChartsDataPayload;
return prepareStatusCodeBarChartsConfig({
timezone,
isDarkMode,
graphClickHandler,
getCustomSeries,
query: currentQuery,
onDragSelect,
onClick: graphClickHandler,
apiResponse,
minTimeScale: minTime,
maxTimeScale: maxTime,
yAxisUnit: statusCodeWidgetInfo[currentWidgetInfoIndex].yAxisUnit,
colorMapping,
currentQuery,
],
);
});
}, [
currentQuery,
isDarkMode,
minTime,
maxTime,
graphClickHandler,
onDragSelect,
formattedEndPointStatusCodeBarChartsDataPayload,
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
timezone,
currentWidgetInfoIndex,
colorMapping,
]);
const renderCardContent = useCallback(
(query: UseQueryResult<SuccessResponse<any>, unknown>): JSX.Element => {
@@ -253,11 +227,20 @@ function StatusCodeBarCharts({
!query.isLoading && !query?.data?.payload?.data?.result?.length,
})}
>
<Uplot options={options as Options} data={chartData} />
<BarChart
config={config}
data={chartData}
width={dimensions.width}
height={dimensions.height}
timezone={timezone}
legendConfig={{
position: LegendPosition.BOTTOM,
}}
/>
</div>
);
},
[options, chartData],
[config, chartData, dimensions, timezone],
);
return (

View File

@@ -0,0 +1,83 @@
import { ExecStats } from 'api/v5/v5';
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { buildBaseConfig } from 'container/DashboardContainer/visualization/panels/utils/baseConfigBuilder';
import { getLegend } from 'lib/dashboard/getQueryResults';
import getLabelName from 'lib/getLabelName';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { DrawStyle } from 'lib/uPlotV2/config/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import { get } from 'lodash-es';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryData } from 'types/api/widgets/getQuery';
import { v4 } from 'uuid';
export const prepareStatusCodeBarChartsConfig = ({
timezone,
isDarkMode,
query,
onDragSelect,
onClick,
apiResponse,
minTimeScale,
maxTimeScale,
yAxisUnit,
colorMapping,
}: {
timezone: Timezone;
isDarkMode: boolean;
query: Query;
onDragSelect: (startTime: number, endTime: number) => void;
onClick?: OnClickPluginOpts['onClick'];
minTimeScale?: number;
maxTimeScale?: number;
apiResponse: MetricRangePayloadProps;
yAxisUnit?: string;
colorMapping?: Record<string, string>;
}): UPlotConfigBuilder => {
const stepIntervals: ExecStats['stepIntervals'] = get(
apiResponse,
'data.newResult.meta.stepIntervals',
{},
);
const minStepInterval = Math.min(...Object.values(stepIntervals));
const config = buildBaseConfig({
id: v4(),
yAxisUnit: yAxisUnit,
apiResponse,
isDarkMode,
onDragSelect,
timezone,
onClick,
minTimeScale,
maxTimeScale,
stepInterval: minStepInterval,
panelType: PANEL_TYPES.BAR,
});
const seriesList: QueryData[] = apiResponse?.data?.result || [];
seriesList.forEach((series) => {
const baseLabelName = getLabelName(
series.metric,
series.queryName || '', // query
series.legend || '',
);
const label = query ? getLegend(series, query, baseLabelName) : baseLabelName;
const currentStepInterval = get(stepIntervals, series.queryName, undefined);
config.addSeries({
scaleKey: 'y',
drawStyle: DrawStyle.Bar,
label: label,
colorMapping: colorMapping ?? {},
isDarkMode,
stepInterval: currentStepInterval,
});
});
return config;
};

View File

@@ -21,10 +21,15 @@ interface MockQueryResult {
}
// Mocks
jest.mock('components/Uplot', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => <div data-testid="uplot-mock" />),
}));
jest.mock(
'container/DashboardContainer/visualization/charts/BarChart/BarChart',
() => ({
__esModule: true,
default: jest
.fn()
.mockImplementation(() => <div data-testid="bar-chart-mock" />),
}),
);
jest.mock('components/CeleryTask/useGetGraphCustomSeries', () => ({
useGetGraphCustomSeries: (): { getCustomSeries: jest.Mock } => ({
@@ -70,6 +75,24 @@ jest.mock('hooks/useNotifications', () => ({
useNotifications: (): { notifications: [] } => ({ notifications: [] }),
}));
jest.mock('providers/Timezone', () => ({
useTimezone: (): {
timezone: {
name: string;
value: string;
offset: string;
searchIndex: string;
};
} => ({
timezone: {
name: 'UTC',
value: 'UTC',
offset: '+00:00',
searchIndex: 'UTC',
},
}),
}));
jest.mock('lib/uPlotLib/getUplotChartOptions', () => ({
getUPlotChartOptions: jest.fn().mockReturnValue({}),
}));
@@ -319,7 +342,7 @@ describe('StatusCodeBarCharts', () => {
mockData.payload,
'sum',
);
expect(screen.getByTestId('uplot-mock')).toBeInTheDocument();
expect(screen.getByTestId('bar-chart-mock')).toBeInTheDocument();
expect(screen.getByText('Number of calls')).toBeInTheDocument();
expect(screen.getByText('Latency')).toBeInTheDocument();
});

View File

@@ -337,31 +337,6 @@
.login-submit-btn {
width: 100%;
height: 32px;
padding: 10px 16px;
background: var(--primary);
border: none;
border-radius: 2px;
font-family: Inter, sans-serif;
font-size: 11px;
font-weight: 500;
line-height: 1;
color: var(--bg-neutral-dark-50);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
&:hover:not(:disabled) {
background: var(--primary);
opacity: 0.9;
}
&:disabled {
background: var(--primary);
opacity: 0.6;
cursor: not-allowed;
}
}
.lightMode {

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { Button } from '@signozhq/button';
import { Button } from '@signozhq/ui';
import { Form, Input, Select, Typography } from 'antd';
import getVersion from 'api/v1/version/get';
import get from 'api/v2/sessions/context/get';
@@ -392,9 +392,9 @@ function Login(): JSX.Element {
disabled={!isNextButtonEnabled}
variant="solid"
onClick={onNextHandler}
data-testid="initiate_login"
testId="initiate_login"
className="login-submit-btn"
suffixIcon={<ArrowRight size={12} />}
suffix={<ArrowRight />}
>
Next
</Button>
@@ -406,10 +406,10 @@ function Login(): JSX.Element {
variant="solid"
type="submit"
color="primary"
data-testid="callback_authn_submit"
testId="callback_authn_submit"
data-attr="signup"
className="login-submit-btn"
suffixIcon={<ArrowRight size={12} />}
suffix={<ArrowRight />}
>
Sign in with SSO
</Button>
@@ -420,11 +420,11 @@ function Login(): JSX.Element {
disabled={!isSubmitButtonEnabled}
variant="solid"
color="primary"
data-testid="password_authn_submit"
testId="password_authn_submit"
type="submit"
data-attr="signup"
className="login-submit-btn"
suffixIcon={<ArrowRight size={12} />}
suffix={<ArrowRight />}
>
Sign in with Password
</Button>

View File

@@ -6,7 +6,6 @@ import { Check, ChevronDown, Plus } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import type { MenuProps } from 'antd';
import { Dropdown } from 'antd';
import getPendingInvites from 'api/v1/invite/get';
import getAll from 'api/v1/user/get';
import EditMemberDrawer from 'components/EditMemberDrawer/EditMemberDrawer';
import InviteMembersModal from 'components/InviteMembersModal/InviteMembersModal';
@@ -14,7 +13,7 @@ import MembersTable, { MemberRow } from 'components/MembersTable/MembersTable';
import useUrlQuery from 'hooks/useUrlQuery';
import { useAppContext } from 'providers/App/App';
import { FilterMode, INVITE_PREFIX, MemberStatus } from './utils';
import { FilterMode, MemberStatus, toMemberStatus } from './utils';
import './MembersSettings.styles.scss';
@@ -34,51 +33,24 @@ function MembersSettings(): JSX.Element {
const [isInviteModalOpen, setIsInviteModalOpen] = useState(false);
const [selectedMember, setSelectedMember] = useState<MemberRow | null>(null);
const {
data: usersData,
isLoading: isUsersLoading,
refetch: refetchUsers,
} = useQuery({
const { data: usersData, isLoading, refetch: refetchUsers } = useQuery({
queryFn: getAll,
queryKey: ['getOrgUser', org?.[0]?.id],
});
const {
data: invitesData,
isLoading: isInvitesLoading,
refetch: refetchInvites,
} = useQuery({
queryFn: getPendingInvites,
queryKey: ['getPendingInvites'],
});
const isLoading = isUsersLoading || isInvitesLoading;
const allMembers = useMemo((): MemberRow[] => {
const activeMembers: MemberRow[] = (usersData?.data ?? []).map((user) => ({
id: user.id,
name: user.displayName,
email: user.email,
role: user.role,
status: MemberStatus.Active,
joinedOn: user.createdAt ? String(user.createdAt) : null,
updatedAt: user?.updatedAt ? String(user.updatedAt) : null,
}));
const pendingInvites: MemberRow[] = (invitesData?.data ?? []).map(
(invite) => ({
id: `${INVITE_PREFIX}${invite.id}`,
name: invite.name ?? '',
email: invite.email,
role: invite.role,
status: MemberStatus.Invited,
joinedOn: invite.createdAt ? String(invite.createdAt) : null,
token: invite.token ?? null,
}),
);
return [...activeMembers, ...pendingInvites];
}, [usersData, invitesData]);
const allMembers = useMemo(
(): MemberRow[] =>
(usersData?.data ?? []).map((user) => ({
id: user.id,
name: user.displayName,
email: user.email,
role: user.role,
status: toMemberStatus(user.status ?? ''),
joinedOn: user.createdAt ? String(user.createdAt) : null,
updatedAt: user.updatedAt ? String(user.updatedAt) : null,
})),
[usersData],
);
const filteredMembers = useMemo((): MemberRow[] => {
let result = allMembers;
@@ -100,11 +72,6 @@ function MembersSettings(): JSX.Element {
return result;
}, [allMembers, filterMode, searchQuery]);
const paginatedMembers = useMemo((): MemberRow[] => {
const start = (currentPage - 1) * PAGE_SIZE;
return filteredMembers.slice(start, start + PAGE_SIZE);
}, [filteredMembers, currentPage]);
// TODO(nuqs): Replace with nuqs once the nuqs setup and integration is done
const setPage = useCallback(
(page: number): void => {
@@ -124,7 +91,9 @@ function MembersSettings(): JSX.Element {
}
}, [filteredMembers.length, currentPage, setPage]);
const pendingCount = invitesData?.data?.length ?? 0;
const pendingCount = allMembers.filter(
(m) => m.status === MemberStatus.Invited,
).length;
const totalCount = allMembers.length;
const filterMenuItems: MenuProps['items'] = [
@@ -163,8 +132,7 @@ function MembersSettings(): JSX.Element {
const handleInviteComplete = useCallback((): void => {
refetchUsers();
refetchInvites();
}, [refetchUsers, refetchInvites]);
}, [refetchUsers]);
const handleRowClick = useCallback((member: MemberRow): void => {
setSelectedMember(member);
@@ -176,9 +144,8 @@ function MembersSettings(): JSX.Element {
const handleMemberEditComplete = useCallback((): void => {
refetchUsers();
refetchInvites();
setSelectedMember(null);
}, [refetchUsers, refetchInvites]);
}, [refetchUsers]);
return (
<>
@@ -232,7 +199,7 @@ function MembersSettings(): JSX.Element {
</div>
</div>
<MembersTable
data={paginatedMembers}
data={filteredMembers}
loading={isLoading}
total={filteredMembers.length}
currentPage={currentPage}
@@ -253,7 +220,6 @@ function MembersSettings(): JSX.Element {
open={selectedMember !== null}
onClose={handleDrawerClose}
onComplete={handleMemberEditComplete}
onRefetch={handleInviteComplete}
/>
</>
);

View File

@@ -1,6 +1,5 @@
import { rest, server } from 'mocks-server/server';
import { render, screen, userEvent } from 'tests/test-utils';
import { PendingInvite } from 'types/api/user/getPendingInvites';
import { UserResponse } from 'types/api/user/getUser';
import MembersSettings from '../MembersSettings';
@@ -13,7 +12,6 @@ jest.mock('@signozhq/sonner', () => ({
}));
const USERS_ENDPOINT = '*/api/v1/user';
const INVITES_ENDPOINT = '*/api/v1/invite';
const mockUsers: UserResponse[] = [
{
@@ -21,7 +19,8 @@ const mockUsers: UserResponse[] = [
displayName: 'Alice Smith',
email: 'alice@signoz.io',
role: 'ADMIN',
createdAt: 1700000000,
status: 'active',
createdAt: '2024-01-01T00:00:00.000Z',
organization: 'TestOrg',
orgId: 'org-1',
},
@@ -30,20 +29,30 @@ const mockUsers: UserResponse[] = [
displayName: 'Bob Jones',
email: 'bob@signoz.io',
role: 'VIEWER',
createdAt: 1700000001,
status: 'active',
createdAt: '2024-01-02T00:00:00.000Z',
organization: 'TestOrg',
orgId: 'org-1',
},
];
const mockInvites: PendingInvite[] = [
{
id: 'inv-1',
displayName: '',
email: 'charlie@signoz.io',
name: 'Charlie',
role: 'EDITOR',
createdAt: 1700000002,
token: 'tok-abc',
status: 'pending_invite',
createdAt: '2024-01-03T00:00:00.000Z',
organization: 'TestOrg',
orgId: 'org-1',
},
{
id: 'user-3',
displayName: 'Dave Deleted',
email: 'dave@signoz.io',
role: 'VIEWER',
status: 'deleted',
createdAt: '2024-01-04T00:00:00.000Z',
organization: 'TestOrg',
orgId: 'org-1',
},
];
@@ -54,9 +63,6 @@ describe('MembersSettings (integration)', () => {
rest.get(USERS_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ data: mockUsers })),
),
rest.get(INVITES_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json({ data: mockInvites })),
),
);
});
@@ -64,14 +70,16 @@ describe('MembersSettings (integration)', () => {
server.resetHandlers();
});
it('loads and displays active users and pending invites', async () => {
it('loads and displays active users, pending invites, and deleted members', async () => {
render(<MembersSettings />);
await screen.findByText('Alice Smith');
expect(screen.getByText('Bob Jones')).toBeInTheDocument();
expect(screen.getByText('charlie@signoz.io')).toBeInTheDocument();
expect(screen.getByText('Dave Deleted')).toBeInTheDocument();
expect(screen.getAllByText('ACTIVE')).toHaveLength(2);
expect(screen.getByText('INVITED')).toBeInTheDocument();
expect(screen.getByText('DELETED')).toBeInTheDocument();
});
it('filters to pending invites via the filter dropdown', async () => {
@@ -107,7 +115,7 @@ describe('MembersSettings (integration)', () => {
expect(screen.queryByText('charlie@signoz.io')).not.toBeInTheDocument();
});
it('opens EditMemberDrawer when a member row is clicked', async () => {
it('opens EditMemberDrawer when an active member row is clicked', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<MembersSettings />);
@@ -117,6 +125,16 @@ describe('MembersSettings (integration)', () => {
await screen.findByText('Member Details');
});
it('does not open EditMemberDrawer when a deleted member row is clicked', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<MembersSettings />);
await user.click(await screen.findByText('Dave Deleted'));
expect(screen.queryByText('Member Details')).not.toBeInTheDocument();
});
it('opens InviteMembersModal when "Invite member" button is clicked', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });

View File

@@ -1,5 +1,3 @@
export const INVITE_PREFIX = 'invite-';
export enum FilterMode {
All = 'all',
Invited = 'invited',
@@ -8,4 +6,25 @@ export enum FilterMode {
export enum MemberStatus {
Active = 'Active',
Invited = 'Invited',
Deleted = 'Deleted',
Anonymous = 'Anonymous',
}
export enum UserApiStatus {
Active = 'active',
PendingInvite = 'pending_invite',
Deleted = 'deleted',
}
export function toMemberStatus(apiStatus: string): MemberStatus {
switch (apiStatus) {
case UserApiStatus.PendingInvite:
return MemberStatus.Invited;
case UserApiStatus.Deleted:
return MemberStatus.Deleted;
case UserApiStatus.Active:
return MemberStatus.Active;
default:
return MemberStatus.Anonymous;
}
}

View File

@@ -1,25 +0,0 @@
export const inviteUser = {
status: 'success',
data: {
statusCode: 200,
error: null,
payload: [
{
email: 'jane@doe.com',
name: 'Jane',
token: 'testtoken',
createdAt: 1715741587,
role: 'VIEWER',
organization: 'test',
},
{
email: 'test+in@singoz.io',
name: '',
token: 'testtoken1',
createdAt: 1720095913,
role: 'VIEWER',
organization: 'test',
},
],
},
};

View File

@@ -9,7 +9,6 @@ import {
getDashboardById,
} from './__mockdata__/dashboards';
import { explorerView } from './__mockdata__/explorer_views';
import { inviteUser } from './__mockdata__/invite_user';
import { licensesSuccessResponse } from './__mockdata__/licenses';
import { membersResponse } from './__mockdata__/members';
import { queryRangeSuccessResponse } from './__mockdata__/query_range';
@@ -175,11 +174,14 @@ export const handlers = [
res(ctx.status(200), ctx.json(getDashboardById)),
),
rest.get('http://localhost/api/v1/invite', (_, res, ctx) =>
res(ctx.status(200), ctx.json(inviteUser)),
),
rest.post('http://localhost/api/v1/invite', (_, res, ctx) =>
res(ctx.status(200), ctx.json(inviteUser)),
res(
ctx.status(200),
ctx.json({
status: 'success',
data: 'invite sent successfully',
}),
),
),
rest.put('http://localhost/api/v1/user/:id', (_, res, ctx) =>
res(

View File

@@ -1,13 +1,9 @@
import { useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { useMemo, useState } from 'react';
import { Button } from '@signozhq/button';
import { Callout } from '@signozhq/callout';
import { Input } from '@signozhq/input';
import { Form, Input as AntdInput, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import accept from 'api/v1/invite/id/accept';
import getInviteDetails from 'api/v1/invite/id/get';
import signUpApi from 'api/v1/register/post';
import passwordAuthNContext from 'api/v2/sessions/email_password/post';
import afterLogin from 'AppRoutes/utils';
@@ -15,9 +11,7 @@ import AuthError from 'components/AuthError/AuthError';
import AuthPageContainer from 'components/AuthPageContainer';
import { useNotifications } from 'hooks/useNotifications';
import { ArrowRight, CircleAlert } from 'lucide-react';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { InviteDetails } from 'types/api/user/getInviteDetails';
import { FormContainer, Label } from './styles';
@@ -39,22 +33,6 @@ function SignUp(): JSX.Element {
false,
);
const [formError, setFormError] = useState<APIError | null>();
const { search } = useLocation();
const params = new URLSearchParams(search);
const token = params.get('token');
const [isDetailsDisable, setIsDetailsDisable] = useState<boolean>(false);
const getInviteDetailsResponse = useQuery<
SuccessResponseV2<InviteDetails>,
APIError
>({
queryFn: () =>
getInviteDetails({
inviteId: token || '',
}),
queryKey: ['getInviteDetails', token],
enabled: token !== null,
});
const { notifications } = useNotifications();
const [form] = Form.useForm<FormValues>();
@@ -64,49 +42,6 @@ function SignUp(): JSX.Element {
const password = Form.useWatch('password', form);
const confirmPassword = Form.useWatch('confirmPassword', form);
useEffect(() => {
if (
getInviteDetailsResponse.status === 'success' &&
getInviteDetailsResponse.data.data
) {
const responseDetails = getInviteDetailsResponse.data.data;
form.setFieldValue('email', responseDetails.email);
form.setFieldValue('organizationName', responseDetails.organization);
setIsDetailsDisable(true);
logEvent('Account Creation Page Visited', {
email: responseDetails.email,
name: responseDetails.name,
company_name: responseDetails.organization,
source: 'SigNoz Cloud',
});
}
}, [
getInviteDetailsResponse.data?.data,
form,
getInviteDetailsResponse.status,
]);
useEffect(() => {
if (
getInviteDetailsResponse.status === 'success' &&
getInviteDetailsResponse?.error
) {
const { error } = getInviteDetailsResponse;
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
}, [
getInviteDetailsResponse,
getInviteDetailsResponse.data,
getInviteDetailsResponse.status,
notifications,
]);
const isSignUp = token === null;
const signUp = async (values: FormValues): Promise<void> => {
try {
const { organizationName, password, email } = values;
@@ -114,7 +49,6 @@ function SignUp(): JSX.Element {
email,
orgDisplayName: organizationName,
password,
token: params.get('token') || undefined,
});
const token = await passwordAuthNContext({
@@ -129,25 +63,6 @@ function SignUp(): JSX.Element {
}
};
const acceptInvite = async (values: FormValues): Promise<void> => {
try {
const { password, email } = values;
const user = await accept({
password,
token: params.get('token') || '',
});
const token = await passwordAuthNContext({
email,
password,
orgId: user.data.orgId,
});
await afterLogin(token.data.accessToken, token.data.refreshToken);
} catch (error) {
setFormError(error as APIError);
}
};
const handleSubmit = (): void => {
(async (): Promise<void> => {
try {
@@ -155,14 +70,10 @@ function SignUp(): JSX.Element {
setLoading(true);
setFormError(null);
if (isSignUp) {
await signUp(values);
logEvent('Account Created Successfully', {
email: values.email,
});
} else {
await acceptInvite(values);
}
await signUp(values);
logEvent('Account Created Successfully', {
email: values.email,
});
setLoading(false);
} catch (error) {
@@ -247,7 +158,6 @@ function SignUp(): JSX.Element {
autoFocus
required
id="signupEmail"
disabled={isDetailsDisable}
className="signup-form-input"
/>
</FormContainer.Item>
@@ -291,15 +201,13 @@ function SignUp(): JSX.Element {
</div>
</div>
{isSignUp && (
<Callout
type="info"
size="small"
showIcon
className="signup-info-callout"
description="This will create an admin account. If you are not an admin, please ask your admin for an invite link"
/>
)}
<Callout
type="info"
size="small"
showIcon
className="signup-info-callout"
description="This will create an admin account. If you are not an admin, please ask your admin for an invite link"
/>
{confirmPasswordError && (
<Callout

View File

@@ -1,7 +1,6 @@
import afterLogin from 'AppRoutes/utils';
import { rest, server } from 'mocks-server/server';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { InviteDetails } from 'types/api/user/getInviteDetails';
import { SignupResponse } from 'types/api/v1/register/post';
import { Token } from 'types/api/v2/sessions/email_password/post';
@@ -32,14 +31,8 @@ jest.mock('lib/history', () => ({
const REGISTER_ENDPOINT = '*/api/v1/register';
const EMAIL_PASSWORD_ENDPOINT = '*/api/v2/sessions/email_password';
const INVITE_DETAILS_ENDPOINT = '*/api/v1/invite/*';
const ACCEPT_INVITE_ENDPOINT = '*/api/v1/invite/accept';
interface MockSignupResponse extends SignupResponse {
orgId: string;
}
const mockSignupResponse: MockSignupResponse = {
const mockSignupResponse: SignupResponse = {
orgId: 'test-org-id',
createdAt: Date.now(),
email: 'test@signoz.io',
@@ -53,15 +46,6 @@ const mockTokenResponse: Token = {
refreshToken: 'mock-refresh-token',
};
const mockInviteDetails: InviteDetails = {
email: 'invited@signoz.io',
name: 'Invited User',
organization: 'Test Org',
createdAt: Date.now(),
role: 'ADMIN',
token: 'invite-token-123',
};
describe('SignUp Component - Regular Signup', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -288,242 +272,3 @@ describe('SignUp Component - Regular Signup', () => {
});
});
});
describe('SignUp Component - Accept Invite', () => {
beforeEach(() => {
jest.clearAllMocks();
window.history.pushState({}, '', '/signup?token=invite-token-123');
});
afterEach(() => {
server.resetHandlers();
});
describe('Initial Render with Invite', () => {
it('pre-fills form fields from invite details', async () => {
server.use(
rest.get(INVITE_DETAILS_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: mockInviteDetails,
status: 'success',
}),
),
),
);
render(<SignUp />, undefined, {
initialRoute: '/signup?token=invite-token-123',
});
const emailInput = await screen.findByLabelText(/email address/i);
await waitFor(() => {
expect(emailInput).toHaveValue('invited@signoz.io');
});
});
it('disables email field when invite details are loaded', async () => {
server.use(
rest.get(INVITE_DETAILS_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: mockInviteDetails,
status: 'success',
}),
),
),
);
render(<SignUp />, undefined, {
initialRoute: '/signup?token=invite-token-123',
});
const emailInput = await screen.findByLabelText(/email address/i);
await waitFor(() => {
expect(emailInput).toBeDisabled();
});
});
it('does not show admin account info callout for invite flow', async () => {
server.use(
rest.get(INVITE_DETAILS_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: mockInviteDetails,
status: 'success',
}),
),
),
);
render(<SignUp />, undefined, {
initialRoute: '/signup?token=invite-token-123',
});
await waitFor(() => {
expect(
screen.queryByText(/this will create an admin account/i),
).not.toBeInTheDocument();
});
});
});
describe('Successful Invite Acceptance', () => {
it('successfully accepts invite and logs in user', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.get(INVITE_DETAILS_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: mockInviteDetails,
status: 'success',
}),
),
),
rest.post(ACCEPT_INVITE_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: mockSignupResponse,
status: 'success',
}),
),
),
rest.post(EMAIL_PASSWORD_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: mockTokenResponse,
status: 'success',
}),
),
),
);
render(<SignUp />, undefined, {
initialRoute: '/signup?token=invite-token-123',
});
const emailInput = await screen.findByLabelText(/email address/i);
await waitFor(() => {
expect(emailInput).toHaveValue('invited@signoz.io');
});
const passwordInput = screen.getByPlaceholderText(/enter new password/i);
const confirmPasswordInput = screen.getByPlaceholderText(
/confirm your new password/i,
);
const submitButton = screen.getByRole('button', {
name: /access my workspace/i,
});
await user.type(passwordInput, 'password123');
await user.type(confirmPasswordInput, 'password123');
await waitFor(() => {
expect(submitButton).not.toBeDisabled();
});
await user.click(submitButton);
await waitFor(() => {
expect(mockAfterLogin).toHaveBeenCalledWith(
'mock-access-token',
'mock-refresh-token',
);
});
});
});
describe('Error Handling for Invite', () => {
it('displays error when invite details fetch fails', async () => {
server.use(
rest.get(INVITE_DETAILS_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(404),
ctx.json({
error: {
code: 'INVITE_NOT_FOUND',
message: 'Invite not found',
},
}),
),
),
);
render(<SignUp />, undefined, {
initialRoute: '/signup?token=invalid-token',
});
// Verify form is still accessible and fields are enabled
const emailInput = await screen.findByLabelText(/email address/i);
expect(emailInput).toBeInTheDocument();
expect(emailInput).not.toBeDisabled();
});
it('displays error when accept invite API fails', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.get(INVITE_DETAILS_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
data: mockInviteDetails,
status: 'success',
}),
),
),
rest.post(ACCEPT_INVITE_ENDPOINT, (_req, res, ctx) =>
res(
ctx.status(400),
ctx.json({
error: {
code: 'INVALID_TOKEN',
message: 'Invalid or expired invite token',
},
}),
),
),
);
render(<SignUp />, undefined, {
initialRoute: '/signup?token=expired-token',
});
const emailInput = await screen.findByLabelText(/email address/i);
await waitFor(() => {
expect(emailInput).toHaveValue('invited@signoz.io');
});
const passwordInput = screen.getByPlaceholderText(/enter new password/i);
const confirmPasswordInput = screen.getByPlaceholderText(
/confirm your new password/i,
);
const submitButton = screen.getByRole('button', {
name: /access my workspace/i,
});
await user.type(passwordInput, 'password123');
await user.type(confirmPasswordInput, 'password123');
await waitFor(() => {
expect(submitButton).not.toBeDisabled();
});
await user.click(submitButton);
expect(
await screen.findByText(/invalid or expired invite token/i),
).toBeInTheDocument();
});
});
});

View File

@@ -1,20 +0,0 @@
import { UserResponse } from './getUser';
export interface Props {
token: string;
password: string;
displayName?: string;
sourceUrl?: string;
}
export interface LoginPrecheckResponse {
sso: boolean;
ssoUrl?: string;
canSelfRegister?: boolean;
isUser: boolean;
}
export interface PayloadProps {
data: UserResponse;
status: string;
}

View File

@@ -1,7 +0,0 @@
export interface Props {
id: string;
}
export interface PayloadProps {
data: string;
}

View File

@@ -1,22 +0,0 @@
import { User } from 'types/reducer/app';
import { ROLES } from 'types/roles';
import { Organization } from './getOrganization';
export interface Props {
inviteId: string;
}
export interface PayloadProps {
data: InviteDetails;
status: string;
}
export interface InviteDetails {
createdAt: number;
email: User['email'];
name: User['displayName'];
role: ROLES;
token: string;
organization: Organization['displayName'];
}

View File

@@ -1,16 +0,0 @@
import { User } from 'types/reducer/app';
import { ROLES } from 'types/roles';
export interface PendingInvite {
createdAt: number;
email: User['email'];
name: User['displayName'];
role: ROLES;
id: string;
token: string;
}
export type PayloadProps = {
data: PendingInvite[];
status: string;
};

View File

@@ -7,14 +7,16 @@ export interface Props {
}
export interface UserResponse {
createdAt: number;
createdAt: number | string;
email: string;
id: string;
displayName: string;
orgId: string;
organization: string;
role: ROLES;
updatedAt?: number;
updatedAt?: number | string;
isRoot?: boolean;
status?: 'active' | 'pending_invite' | 'deleted';
}
export interface PayloadProps {
data: UserResponse;

View File

@@ -1,9 +1,8 @@
import { sentryVitePlugin } from '@sentry/vite-plugin';
import react from '@vitejs/plugin-react';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import type { Plugin, UserConfig } from 'vite';
import type { Plugin, TransformResult, UserConfig } from 'vite';
import { defineConfig, loadEnv } from 'vite';
import vitePluginChecker from 'vite-plugin-checker';
import viteCompression from 'vite-plugin-compression';
@@ -14,15 +13,14 @@ import tsconfigPaths from 'vite-tsconfig-paths';
function rawMarkdownPlugin(): Plugin {
return {
name: 'raw-markdown',
transform(_, id): any {
if (id.endsWith('.md')) {
const content = readFileSync(id, 'utf-8');
return {
code: `export default ${JSON.stringify(content)};`,
map: null,
};
transform(code, id): TransformResult | undefined {
if (!id.endsWith('.md')) {
return undefined;
}
return undefined;
return {
code: `export default ${JSON.stringify(code)};`,
map: null,
};
},
};
}
@@ -71,7 +69,7 @@ export default defineConfig(
);
}
if (env.NODE_ENV === 'production') {
if (mode === 'production') {
plugins.push(
ViteImageOptimizer({
jpeg: { quality: 80 },
@@ -102,22 +100,25 @@ export default defineConfig(
},
define: {
// TODO: Remove this in favor of import.meta.env
'process.env': JSON.stringify({
NODE_ENV: mode,
FRONTEND_API_ENDPOINT: env.VITE_FRONTEND_API_ENDPOINT,
WEBSOCKET_API_ENDPOINT: env.VITE_WEBSOCKET_API_ENDPOINT,
PYLON_APP_ID: env.VITE_PYLON_APP_ID,
PYLON_IDENTITY_SECRET: env.VITE_PYLON_IDENTITY_SECRET,
APPCUES_APP_ID: env.VITE_APPCUES_APP_ID,
POSTHOG_KEY: env.VITE_POSTHOG_KEY,
SENTRY_AUTH_TOKEN: env.VITE_SENTRY_AUTH_TOKEN,
SENTRY_ORG: env.VITE_SENTRY_ORG,
SENTRY_PROJECT_ID: env.VITE_SENTRY_PROJECT_ID,
SENTRY_DSN: env.VITE_SENTRY_DSN,
TUNNEL_URL: env.VITE_TUNNEL_URL,
TUNNEL_DOMAIN: env.VITE_TUNNEL_DOMAIN,
DOCS_BASE_URL: env.VITE_DOCS_BASE_URL,
}),
'process.env.NODE_ENV': JSON.stringify(mode),
'process.env.FRONTEND_API_ENDPOINT': JSON.stringify(
env.VITE_FRONTEND_API_ENDPOINT,
),
'process.env.WEBSOCKET_API_ENDPOINT': JSON.stringify(
env.VITE_WEBSOCKET_API_ENDPOINT,
),
'process.env.PYLON_APP_ID': JSON.stringify(env.VITE_PYLON_APP_ID),
'process.env.PYLON_IDENTITY_SECRET': JSON.stringify(
env.VITE_PYLON_IDENTITY_SECRET,
),
'process.env.APPCUES_APP_ID': JSON.stringify(env.VITE_APPCUES_APP_ID),
'process.env.POSTHOG_KEY': JSON.stringify(env.VITE_POSTHOG_KEY),
'process.env.SENTRY_ORG': JSON.stringify(env.VITE_SENTRY_ORG),
'process.env.SENTRY_PROJECT_ID': JSON.stringify(env.VITE_SENTRY_PROJECT_ID),
'process.env.SENTRY_DSN': JSON.stringify(env.VITE_SENTRY_DSN),
'process.env.TUNNEL_URL': JSON.stringify(env.VITE_TUNNEL_URL),
'process.env.TUNNEL_DOMAIN': JSON.stringify(env.VITE_TUNNEL_DOMAIN),
'process.env.DOCS_BASE_URL': JSON.stringify(env.VITE_DOCS_BASE_URL),
},
build: {
sourcemap: true,

View File

@@ -4506,6 +4506,19 @@
"@radix-ui/react-use-callback-ref" "1.1.1"
"@radix-ui/react-use-escape-keydown" "1.1.1"
"@radix-ui/react-dropdown-menu@^2.1.16":
version "2.1.16"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz#5ee045c62bad8122347981c479d92b1ff24c7254"
integrity sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==
dependencies:
"@radix-ui/primitive" "1.1.3"
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-context" "1.1.2"
"@radix-ui/react-id" "1.1.1"
"@radix-ui/react-menu" "2.1.16"
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-use-controllable-state" "1.2.2"
"@radix-ui/react-focus-guards@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz#339c1c69c41628c1a5e655f15f7020bf11aa01fa"
@@ -4565,6 +4578,30 @@
dependencies:
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-menu@2.1.16":
version "2.1.16"
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.16.tgz#528a5a973c3a7413d3d49eb9ccd229aa52402911"
integrity sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==
dependencies:
"@radix-ui/primitive" "1.1.3"
"@radix-ui/react-collection" "1.1.7"
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-context" "1.1.2"
"@radix-ui/react-direction" "1.1.1"
"@radix-ui/react-dismissable-layer" "1.1.11"
"@radix-ui/react-focus-guards" "1.1.3"
"@radix-ui/react-focus-scope" "1.1.7"
"@radix-ui/react-id" "1.1.1"
"@radix-ui/react-popper" "1.2.8"
"@radix-ui/react-portal" "1.1.9"
"@radix-ui/react-presence" "1.1.5"
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-roving-focus" "1.1.11"
"@radix-ui/react-slot" "1.2.3"
"@radix-ui/react-use-callback-ref" "1.1.1"
aria-hidden "^1.2.4"
react-remove-scroll "^2.6.3"
"@radix-ui/react-popover@^1.1.15", "@radix-ui/react-popover@^1.1.2":
version "1.1.15"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz#9c852f93990a687ebdc949b2c3de1f37cdc4c5d5"
@@ -4804,6 +4841,20 @@
"@radix-ui/react-roving-focus" "1.0.4"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-tabs@^1.1.3":
version "1.1.13"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz#3537ce379d7e7ff4eeb6b67a0973e139c2ac1f15"
integrity sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==
dependencies:
"@radix-ui/primitive" "1.1.3"
"@radix-ui/react-context" "1.1.2"
"@radix-ui/react-direction" "1.1.1"
"@radix-ui/react-id" "1.1.1"
"@radix-ui/react-presence" "1.1.5"
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-roving-focus" "1.1.11"
"@radix-ui/react-use-controllable-state" "1.2.2"
"@radix-ui/react-toggle-group@^1.1.7":
version "1.1.11"
resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz#e513d6ffdb07509b400ab5b26f2523747c0d51c1"
@@ -5675,6 +5726,42 @@
tailwind-merge "^2.5.2"
tailwindcss-animate "^1.0.7"
"@signozhq/ui@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@signozhq/ui/-/ui-0.0.5.tgz#8badef53416b7ace0fe61ff01ff3da679a0e4ba5"
integrity sha512-4vPvUh3rwpst068qXUZ26JfCQGv1vo1xMSwtKw6wTjiiq1Bf3geP84HWVXycNMIrIeVnUgDGnqe0D4doh+mL8A==
dependencies:
"@radix-ui/react-checkbox" "^1.2.3"
"@radix-ui/react-dialog" "^1.1.11"
"@radix-ui/react-dropdown-menu" "^2.1.16"
"@radix-ui/react-icons" "^1.3.0"
"@radix-ui/react-popover" "^1.1.15"
"@radix-ui/react-radio-group" "^1.3.4"
"@radix-ui/react-slot" "^1.2.3"
"@radix-ui/react-switch" "^1.1.4"
"@radix-ui/react-tabs" "^1.1.3"
"@radix-ui/react-toggle" "^1.1.6"
"@radix-ui/react-toggle-group" "^1.1.7"
"@radix-ui/react-tooltip" "^1.2.6"
"@tanstack/react-table" "^8.21.3"
"@tanstack/react-virtual" "^3.13.9"
"@types/lodash-es" "^4.17.12"
class-variance-authority "^0.7.0"
clsx "^2.1.1"
cmdk "^1.1.1"
date-fns "^4.1.0"
dayjs "^1.11.10"
lodash-es "^4.17.21"
lucide-react "^0.445.0"
lucide-solid "^0.510.0"
motion "^11.11.17"
next-themes "^0.4.6"
nuqs "^2.8.9"
react-day-picker "^9.8.1"
react-resizable-panels "^4.7.1"
sonner "^2.0.7"
tailwind-merge "^3.5.0"
"@sinclair/typebox@^0.25.16":
version "0.25.24"
resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz"
@@ -9573,6 +9660,11 @@ dayjs@^1.10.7, dayjs@^1.11.1:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
dayjs@^1.11.10:
version "1.11.20"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.20.tgz#88d919fd639dc991415da5f4cb6f1b6650811938"
integrity sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==
debounce@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
@@ -11092,6 +11184,15 @@ fraction.js@^4.3.7:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
framer-motion@^11.18.2:
version "11.18.2"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.18.2.tgz#0c6bd05677f4cfd3b3bdead4eb5ecdd5ed245718"
integrity sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==
dependencies:
motion-dom "^11.18.1"
motion-utils "^11.18.1"
tslib "^2.4.0"
framer-motion@^12.4.13:
version "12.4.13"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.4.13.tgz#1efd954f95e6a54685b660929c00f5a61e35256a"
@@ -15002,6 +15103,13 @@ moment@^2.29.4:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
motion-dom@^11.18.1:
version "11.18.1"
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-11.18.1.tgz#e7fed7b7dc6ae1223ef1cce29ee54bec826dc3f2"
integrity sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==
dependencies:
motion-utils "^11.18.1"
motion-dom@^12.4.11:
version "12.4.11"
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.4.11.tgz#0419c8686cda4d523f08249deeb8fa6683a9b9d3"
@@ -15009,6 +15117,11 @@ motion-dom@^12.4.11:
dependencies:
motion-utils "^12.4.10"
motion-utils@^11.18.1:
version "11.18.1"
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-11.18.1.tgz#671227669833e991c55813cf337899f41327db5b"
integrity sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==
motion-utils@^12.4.10:
version "12.4.10"
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.4.10.tgz#3d93acea5454419eaaad8d5e5425cb71cbfa1e7f"
@@ -15022,6 +15135,14 @@ motion@12.4.13:
framer-motion "^12.4.13"
tslib "^2.4.0"
motion@^11.11.17:
version "11.18.2"
resolved "https://registry.yarnpkg.com/motion/-/motion-11.18.2.tgz#17fb372f3ed94fc9ee1384a25a9068e9da1951e7"
integrity sha512-JLjvFDuFr42NFtcVoMAyC2sEjnpA8xpy6qWPyzQvCloznAyQ8FIXioxWfHiLtgYhoVpfUqSWpn1h9++skj9+Wg==
dependencies:
framer-motion "^11.18.2"
tslib "^2.4.0"
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@@ -15292,6 +15413,13 @@ nuqs@2.8.8:
dependencies:
"@standard-schema/spec" "1.0.0"
nuqs@^2.8.9:
version "2.8.9"
resolved "https://registry.yarnpkg.com/nuqs/-/nuqs-2.8.9.tgz#e2c27d87c0dd0e3b4412fe867bcd0947cc4c998f"
integrity sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ==
dependencies:
"@standard-schema/spec" "1.0.0"
nwsapi@^2.2.2:
version "2.2.23"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.23.tgz#59712c3a88e6de2bb0b6ccc1070397267019cf6c"
@@ -16957,6 +17085,11 @@ react-resizable-panels@^3.0.5:
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-3.0.5.tgz#50a20645263eed02344de4a70d1319bbc0014bbd"
integrity sha512-3z1yN25DMTXLg2wfyFrW32r5k4WEcUa3F7cJ2EgtNK07lnOs4mpM8yWLGunCpkhcQRwJX4fqoLcIh/pHPxzlmQ==
react-resizable-panels@^4.7.1:
version "4.7.3"
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-4.7.3.tgz#4040aa0f5c5c4cc4bb685cb69973601ccda3b014"
integrity sha512-PYcYMLtvJD+Pr0TQNeMvddcnLOwUa/Yb4iNwU7ThNLlHaQYEEC9MIBWHaBGODzYuXIkPRZ/OWe5sbzG1Rzq5ew==
react-resizable@3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz"
@@ -18797,6 +18930,11 @@ tailwind-merge@^2.5.2:
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.6.0.tgz#ac5fb7e227910c038d458f396b7400d93a3142d5"
integrity sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==
tailwind-merge@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.5.0.tgz#06502f4496ba15151445d97d916a26564d50d1ca"
integrity sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==
tailwindcss-animate@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz#318b692c4c42676cc9e67b19b78775742388bef4"

2
go.mod
View File

@@ -11,7 +11,6 @@ require (
github.com/SigNoz/signoz-otel-collector v0.144.2
github.com/antlr4-go/antlr/v4 v4.13.1
github.com/antonmedv/expr v1.15.3
github.com/bytedance/sonic v1.14.1
github.com/cespare/xxhash/v2 v2.3.0
github.com/coreos/go-oidc/v3 v3.17.0
github.com/dgraph-io/ristretto/v2 v2.3.0
@@ -106,6 +105,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.14.1 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
@@ -21,7 +22,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -38,7 +39,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -55,7 +56,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -72,7 +73,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -25,7 +25,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -42,7 +42,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -59,7 +59,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -76,7 +76,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/gorilla/mux"
)
@@ -23,7 +23,7 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -41,7 +41,7 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/gorilla/mux"
)
@@ -22,7 +22,7 @@ func (provider *provider) addFlaggerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/gatewaytypes"
"github.com/gorilla/mux"
)
@@ -23,7 +23,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -41,7 +41,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -58,7 +58,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -75,7 +75,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -92,7 +92,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -109,7 +109,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -126,7 +126,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -143,7 +143,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
@@ -22,7 +21,7 @@ func (provider *provider) addGlobalRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/metricsexplorertypes"
"github.com/gorilla/mux"
)
@@ -25,7 +25,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -44,7 +44,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -63,7 +63,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -83,7 +83,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -102,7 +102,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -121,7 +121,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -140,7 +140,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -159,7 +159,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -178,7 +178,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
@@ -22,7 +21,7 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -39,7 +38,7 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusConflict, http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/gorilla/mux"
)
@@ -22,7 +22,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -39,7 +39,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -56,7 +56,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -73,7 +73,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -90,7 +90,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -107,7 +107,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/promotetypes"
"github.com/gorilla/mux"
)
@@ -21,7 +21,7 @@ func (provider *provider) addPromoteRoutes(router *mux.Router) error {
ResponseContentType: "",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -37,7 +37,7 @@ func (provider *provider) addPromoteRoutes(router *mux.Router) error {
ResponseContentType: "",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -22,6 +22,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/gorilla/mux"
@@ -235,7 +236,7 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return nil
}
func newSecuritySchemes(role authtypes.LegacyRole) []handler.OpenAPISecurityScheme {
func newSecuritySchemes(role types.Role) []handler.OpenAPISecurityScheme {
return []handler.OpenAPISecurityScheme{
{Name: authtypes.IdentNProviderAPIkey.StringValue(), Scopes: []string{role.String()}},
{Name: authtypes.IdentNProviderTokenizer.StringValue(), Scopes: []string{role.String()}},

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/gorilla/mux"
)
@@ -446,7 +446,7 @@ func (provider *provider) addQuerierRoutes(router *mux.Router) error {
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -462,7 +462,7 @@ func (provider *provider) addQuerierRoutes(router *mux.Router) error {
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}

View File

@@ -22,7 +22,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -39,7 +39,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -56,7 +56,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -73,7 +73,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -90,7 +90,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -107,7 +107,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -124,7 +124,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
"github.com/gorilla/mux"
)
@@ -23,7 +22,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -40,7 +39,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -57,7 +56,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -74,7 +73,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -91,7 +90,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -108,7 +107,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -125,7 +124,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -142,7 +141,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -159,7 +158,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -176,7 +175,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -4,8 +4,8 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/gorilla/mux"
)
@@ -15,14 +15,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Create invite",
Description: "This endpoint creates an invite for a user",
Request: new(usertypes.PostableInvite),
Request: new(types.PostableInvite),
RequestContentType: "application/json",
Response: new(usertypes.Invite),
Response: new(types.Invite),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -32,81 +32,13 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Create bulk invite",
Description: "This endpoint creates a bulk invite for a user",
Request: new(usertypes.PostableBulkInviteRequest),
Request: new(types.PostableBulkInviteRequest),
RequestContentType: "application/json",
Response: nil,
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/invite/{token}", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetInvite), handler.OpenAPIDef{
ID: "GetInvite",
Tags: []string{"users"},
Summary: "Get invite",
Description: "This endpoint gets an invite by token",
Request: nil,
RequestContentType: "",
Response: new(usertypes.Invite),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: []handler.OpenAPISecurityScheme{},
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/invite/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.DeleteInvite), handler.OpenAPIDef{
ID: "DeleteInvite",
Tags: []string{"users"},
Summary: "Delete invite",
Description: "This endpoint deletes an invite by id",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/invite", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListInvite), handler.OpenAPIDef{
ID: "ListInvite",
Tags: []string{"users"},
Summary: "List invites",
Description: "This endpoint lists all invites",
Request: nil,
RequestContentType: "",
Response: make([]*usertypes.Invite, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/invite/accept", handler.New(provider.authZ.OpenAccess(provider.userHandler.AcceptInvite), handler.OpenAPIDef{
ID: "AcceptInvite",
Tags: []string{"users"},
Summary: "Accept invite",
Description: "This endpoint accepts an invite by token",
Request: new(usertypes.PostableAcceptInvite),
RequestContentType: "application/json",
Response: new(usertypes.User),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: []handler.OpenAPISecurityScheme{},
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -116,14 +48,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Create api key",
Description: "This endpoint creates an api key",
Request: new(usertypes.PostableAPIKey),
Request: new(types.PostableAPIKey),
RequestContentType: "application/json",
Response: new(usertypes.GettableAPIKey),
Response: new(types.GettableAPIKey),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -135,12 +67,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint lists all api keys",
Request: nil,
RequestContentType: "",
Response: make([]*usertypes.GettableAPIKey, 0),
Response: make([]*types.GettableAPIKey, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -150,14 +82,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Update api key",
Description: "This endpoint updates an api key",
Request: new(usertypes.StorableAPIKey),
Request: new(types.StorableAPIKey),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -174,7 +106,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -186,12 +118,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint lists all users",
Request: nil,
RequestContentType: "",
Response: make([]*usertypes.GettableUser, 0),
Response: make([]*types.GettableUser, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -203,7 +135,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint returns the user I belong to",
Request: nil,
RequestContentType: "",
Response: new(usertypes.GettableUser),
Response: new(types.GettableUser),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
@@ -220,12 +152,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint returns the user by id",
Request: nil,
RequestContentType: "",
Response: new(usertypes.GettableUser),
Response: new(types.GettableUser),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -235,14 +167,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Update user",
Description: "This endpoint updates the user by id",
Request: new(usertypes.User),
Request: new(types.User),
RequestContentType: "application/json",
Response: new(usertypes.GettableUser),
Response: new(types.GettableUser),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -259,7 +191,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -271,12 +203,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint returns the reset password token by id",
Request: nil,
RequestContentType: "",
Response: new(usertypes.ResetPasswordToken),
Response: new(types.ResetPasswordToken),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -286,7 +218,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Reset password",
Description: "This endpoint resets the password by token",
Request: new(usertypes.PostableResetPassword),
Request: new(types.PostableResetPassword),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
@@ -303,14 +235,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Change password",
Description: "This endpoint changes the password by id",
Request: new(usertypes.ChangePasswordRequest),
Request: new(types.ChangePasswordRequest),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -320,7 +252,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Forgot password",
Description: "This endpoint initiates the forgot password flow by sending a reset password email",
Request: new(usertypes.PostableForgotPassword),
Request: new(types.PostableForgotPassword),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/gorilla/mux"
)
@@ -22,7 +22,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -39,7 +39,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -56,7 +56,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -16,6 +17,37 @@ func NewStore(sqlstore sqlstore.SQLStore) authtypes.AuthNStore {
return &store{sqlstore: sqlstore}
}
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*types.User, *types.FactorPassword, error) {
user := new(types.User)
factorPassword := new(types.FactorPassword)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(user).
Where("email = ?", email).
Where("org_id = ?", orgID).
Where("status = ?", types.UserStatusActive.StringValue()).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with email %s in org %s not found", email, orgID)
}
err = store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(factorPassword).
Where("user_id = ?", user.ID).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodePasswordNotFound, "user with email %s in org %s does not have password", email, orgID)
}
return user, factorPassword, nil
}
func (store *store) GetAuthDomainFromID(ctx context.Context, domainID valuer.UUID) (*authtypes.AuthDomain, error) {
storableAuthDomain := new(authtypes.StorableAuthDomain)

View File

@@ -5,30 +5,29 @@ import (
"github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
var _ authn.PasswordAuthN = (*AuthN)(nil)
type AuthN struct {
userGetter user.Getter
store authtypes.AuthNStore
}
func New(userGetter user.Getter) *AuthN {
return &AuthN{userGetter: userGetter}
func New(store authtypes.AuthNStore) *AuthN {
return &AuthN{store: store}
}
func (a *AuthN) Authenticate(ctx context.Context, email string, password string, orgID valuer.UUID) (*authtypes.Identity, error) {
user, factorPassword, err := a.userGetter.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
user, factorPassword, err := a.store.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
if err != nil {
return nil, err
}
if !factorPassword.Equals(password) {
return nil, errors.New(errors.TypeUnauthenticated, usertypes.ErrCodeIncorrectPassword, "invalid email or password")
return nil, errors.New(errors.TypeUnauthenticated, types.ErrCodeIncorrectPassword, "invalid email or password")
}
return authtypes.NewIdentity(user.ID, orgID, user.Email, user.Role, authtypes.IdentNProviderTokenizer), nil

View File

@@ -9,7 +9,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
@@ -41,9 +40,7 @@ func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
return
}
commentCtx := ctxtypes.CommentFromContext(ctx)
authtype, ok := commentCtx.Map()["auth_type"]
if ok && (authtype == authtypes.IdentNProviderAPIkey.StringValue()) {
if claims.IdentNProvider == authtypes.IdentNProviderAPIkey.StringValue() {
if err := claims.IsViewer(); err != nil {
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
render.Error(rw, err)
@@ -93,9 +90,7 @@ func (middleware *AuthZ) EditAccess(next http.HandlerFunc) http.HandlerFunc {
return
}
commentCtx := ctxtypes.CommentFromContext(ctx)
authtype, ok := commentCtx.Map()["auth_type"]
if ok && (authtype == authtypes.IdentNProviderAPIkey.StringValue()) {
if claims.IdentNProvider == authtypes.IdentNProviderAPIkey.StringValue() {
if err := claims.IsEditor(); err != nil {
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
render.Error(rw, err)
@@ -144,9 +139,7 @@ func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
return
}
commentCtx := ctxtypes.CommentFromContext(ctx)
authtype, ok := commentCtx.Map()["auth_type"]
if ok && (authtype == authtypes.IdentNProviderAPIkey.StringValue()) {
if claims.IdentNProvider == authtypes.IdentNProviderAPIkey.StringValue() {
if err := claims.IsAdmin(); err != nil {
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
render.Error(rw, err)

View File

@@ -9,36 +9,42 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/identn"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"golang.org/x/sync/singleflight"
)
// todo: will move this in types layer with service account integration
type apiKeyTokenKey struct{}
type resolver struct {
type provider struct {
store sqlstore.SQLStore
headers []string
config identn.Config
settings factory.ScopedProviderSettings
sfGroup *singleflight.Group
}
func New(providerSettings factory.ProviderSettings, store sqlstore.SQLStore, headers []string) identn.IdentN {
return &resolver{
store: store,
headers: headers,
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn/apikeyidentn"),
sfGroup: &singleflight.Group{},
}
func NewFactory(store sqlstore.SQLStore) factory.ProviderFactory[identn.IdentN, identn.Config] {
return factory.NewProviderFactory(factory.MustNewName(authtypes.IdentNProviderAPIkey.StringValue()), func(ctx context.Context, providerSettings factory.ProviderSettings, config identn.Config) (identn.IdentN, error) {
return New(providerSettings, store, config)
})
}
func (r *resolver) Name() authtypes.IdentNProvider {
func New(providerSettings factory.ProviderSettings, store sqlstore.SQLStore, config identn.Config) (identn.IdentN, error) {
return &provider{
store: store,
config: config,
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn/apikeyidentn"),
sfGroup: &singleflight.Group{},
}, nil
}
func (provider *provider) Name() authtypes.IdentNProvider {
return authtypes.IdentNProviderAPIkey
}
func (r *resolver) Test(req *http.Request) bool {
for _, header := range r.headers {
func (provider *provider) Test(req *http.Request) bool {
for _, header := range provider.config.APIKeyConfig.Headers {
if req.Header.Get(header) != "" {
return true
}
@@ -46,8 +52,12 @@ func (r *resolver) Test(req *http.Request) bool {
return false
}
func (r *resolver) Pre(req *http.Request) *http.Request {
token := r.extractToken(req)
func (provider *provider) Enabled() bool {
return provider.config.APIKeyConfig.Enabled
}
func (provider *provider) Pre(req *http.Request) *http.Request {
token := provider.extractToken(req)
if token == "" {
return req
}
@@ -56,16 +66,16 @@ func (r *resolver) Pre(req *http.Request) *http.Request {
return req.WithContext(ctx)
}
func (r *resolver) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
ctx := req.Context()
apiKeyToken, ok := ctx.Value(apiKeyTokenKey{}).(string)
if !ok || apiKeyToken == "" {
return nil, errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "missing api key")
}
var apiKey usertypes.StorableAPIKey
err := r.store.
var apiKey types.StorableAPIKey
err := provider.
store.
BunDB().
NewSelect().
Model(&apiKey).
@@ -75,12 +85,13 @@ func (r *resolver) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
return nil, err
}
if apiKey.ExpiresAt.Before(time.Now()) && !apiKey.ExpiresAt.Equal(usertypes.NEVER_EXPIRES) {
if apiKey.ExpiresAt.Before(time.Now()) && !apiKey.ExpiresAt.Equal(types.NEVER_EXPIRES) {
return nil, errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "api key has expired")
}
var user usertypes.User
err = r.store.
var user types.User
err = provider.
store.
BunDB().
NewSelect().
Model(&user).
@@ -90,39 +101,35 @@ func (r *resolver) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
return nil, err
}
identity := authtypes.Identity{
UserID: user.ID,
Role: apiKey.Role,
Email: user.Email,
OrgID: user.OrgID,
}
return &identity, nil
identity := authtypes.NewIdentity(user.ID, user.OrgID, user.Email, apiKey.Role, provider.Name())
return identity, nil
}
func (r *resolver) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
func (provider *provider) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
apiKeyToken, ok := ctx.Value(apiKeyTokenKey{}).(string)
if !ok || apiKeyToken == "" {
return
}
_, _, _ = r.sfGroup.Do(apiKeyToken, func() (any, error) {
_, err := r.store.
_, _, _ = provider.sfGroup.Do(apiKeyToken, func() (any, error) {
_, err := provider.
store.
BunDB().
NewUpdate().
Model(new(usertypes.StorableAPIKey)).
Model(new(types.StorableAPIKey)).
Set("last_used = ?", time.Now()).
Where("token = ?", apiKeyToken).
Where("revoked = false").
Exec(ctx)
if err != nil {
r.settings.Logger().ErrorContext(ctx, "failed to update last used of api key", "error", err)
provider.settings.Logger().ErrorContext(ctx, "failed to update last used of api key", "error", err)
}
return true, nil
})
}
func (r *resolver) extractToken(req *http.Request) string {
for _, header := range r.headers {
func (provider *provider) extractToken(req *http.Request) string {
for _, header := range provider.config.APIKeyConfig.Headers {
if v := req.Header.Get(header); v != "" {
return v
}

48
pkg/identn/config.go Normal file
View File

@@ -0,0 +1,48 @@
package identn
import (
"github.com/SigNoz/signoz/pkg/factory"
)
type Config struct {
// Config for tokenizer identN resolver
Tokenizer TokenizerConfig `mapstructure:"tokenizer"`
// Config for apikey identN resolver
APIKeyConfig APIKeyConfig `mapstructure:"apikey"`
}
type TokenizerConfig struct {
// Toggles the identN resolver
Enabled bool `mapstructure:"enabled"`
// Headers to extract from incoming requests
Headers []string `mapstructure:"headers"`
}
type APIKeyConfig struct {
// Toggles the identN resolver
Enabled bool `mapstructure:"enabled"`
// Headers to extract from incoming requests
Headers []string `mapstructure:"headers"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("identn"), newConfig)
}
func newConfig() factory.Config {
return &Config{
Tokenizer: TokenizerConfig{
Enabled: true,
Headers: []string{"Authorization", "Sec-WebSocket-Protocol"},
},
APIKeyConfig: APIKeyConfig{
Enabled: true,
Headers: []string{"SIGNOZ-API-KEY"},
},
}
}
func (c Config) Validate() error {
return nil
}

View File

@@ -23,6 +23,8 @@ type IdentN interface {
GetIdentity(r *http.Request) (*authtypes.Identity, error)
Name() authtypes.IdentNProvider
Enabled() bool
}
// IdentNWithPreHook is optionally implemented by resolvers that need to

View File

@@ -12,8 +12,16 @@ type identNResolver struct {
}
func NewIdentNResolver(providerSettings factory.ProviderSettings, identNs ...IdentN) IdentNResolver {
enabledIdentNs := []IdentN{}
for _, identN := range identNs {
if identN.Enabled() {
enabledIdentNs = append(enabledIdentNs, identN)
}
}
return &identNResolver{
identNs: identNs,
identNs: enabledIdentNs,
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn"),
}
}

View File

@@ -13,28 +13,34 @@ import (
"golang.org/x/sync/singleflight"
)
type resolver struct {
type provider struct {
tokenizer tokenizer.Tokenizer
headers []string
config identn.Config
settings factory.ScopedProviderSettings
sfGroup *singleflight.Group
}
func New(providerSettings factory.ProviderSettings, tokenizer tokenizer.Tokenizer, headers []string) identn.IdentN {
return &resolver{
tokenizer: tokenizer,
headers: headers,
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn/tokenizeridentn"),
sfGroup: &singleflight.Group{},
}
func NewFactory(tokenizer tokenizer.Tokenizer) factory.ProviderFactory[identn.IdentN, identn.Config] {
return factory.NewProviderFactory(factory.MustNewName(authtypes.IdentNProviderTokenizer.StringValue()), func(ctx context.Context, providerSettings factory.ProviderSettings, config identn.Config) (identn.IdentN, error) {
return New(providerSettings, tokenizer, config)
})
}
func (r *resolver) Name() authtypes.IdentNProvider {
func New(providerSettings factory.ProviderSettings, tokenizer tokenizer.Tokenizer, config identn.Config) (identn.IdentN, error) {
return &provider{
tokenizer: tokenizer,
config: config,
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/identn/tokenizeridentn"),
sfGroup: &singleflight.Group{},
}, nil
}
func (provider *provider) Name() authtypes.IdentNProvider {
return authtypes.IdentNProviderTokenizer
}
func (r *resolver) Test(req *http.Request) bool {
for _, header := range r.headers {
func (provider *provider) Test(req *http.Request) bool {
for _, header := range provider.config.Tokenizer.Headers {
if req.Header.Get(header) != "" {
return true
}
@@ -42,8 +48,12 @@ func (r *resolver) Test(req *http.Request) bool {
return false
}
func (r *resolver) Pre(req *http.Request) *http.Request {
accessToken := r.extractToken(req)
func (provider *provider) Enabled() bool {
return provider.config.Tokenizer.Enabled
}
func (provider *provider) Pre(req *http.Request) *http.Request {
accessToken := provider.extractToken(req)
if accessToken == "" {
return req
}
@@ -52,7 +62,7 @@ func (r *resolver) Pre(req *http.Request) *http.Request {
return req.WithContext(ctx)
}
func (r *resolver) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
ctx := req.Context()
accessToken, err := authtypes.AccessTokenFromContext(ctx)
@@ -60,41 +70,45 @@ func (r *resolver) GetIdentity(req *http.Request) (*authtypes.Identity, error) {
return nil, err
}
return r.tokenizer.GetIdentity(ctx, accessToken)
return provider.tokenizer.GetIdentity(ctx, accessToken)
}
func (r *resolver) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
func (provider *provider) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
if !provider.config.Tokenizer.Enabled {
return
}
accessToken, err := authtypes.AccessTokenFromContext(ctx)
if err != nil {
return
}
_, _, _ = r.sfGroup.Do(accessToken, func() (any, error) {
if err := r.tokenizer.SetLastObservedAt(ctx, accessToken, time.Now()); err != nil {
r.settings.Logger().ErrorContext(ctx, "failed to set last observed at", "error", err)
_, _, _ = provider.sfGroup.Do(accessToken, func() (any, error) {
if err := provider.tokenizer.SetLastObservedAt(ctx, accessToken, time.Now()); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to set last observed at", "error", err)
return false, err
}
return true, nil
})
}
func (r *resolver) extractToken(req *http.Request) string {
func (provider *provider) extractToken(req *http.Request) string {
var value string
for _, header := range r.headers {
for _, header := range provider.config.Tokenizer.Headers {
if v := req.Header.Get(header); v != "" {
value = v
break
}
}
accessToken, ok := r.parseBearerAuth(value)
accessToken, ok := provider.parseBearerAuth(value)
if !ok {
return value
}
return accessToken
}
func (r *resolver) parseBearerAuth(auth string) (string, bool) {
func (provider *provider) parseBearerAuth(auth string) (string, bool) {
const prefix = "Bearer "
if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
return "", false

View File

@@ -0,0 +1,65 @@
package cloudintegration
import (
"context"
"net/http"
citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
CreateAccount(ctx context.Context, account *citypes.Account) error
// GetAccount returns cloud integration account
GetAccount(ctx context.Context, orgID, accountID valuer.UUID) (*citypes.Account, error)
// ListAccounts lists accounts where agent is connected
ListAccounts(ctx context.Context, orgID valuer.UUID) ([]*citypes.Account, error)
// UpdateAccount updates the cloud integration account for a specific organization.
UpdateAccount(ctx context.Context, account *citypes.Account) error
// DisconnectAccount soft deletes/removes a cloud integration account.
DisconnectAccount(ctx context.Context, orgID, accountID valuer.UUID) error
// GetConnectionArtifact returns cloud provider specific connection information,
// client side handles how this information is shown
GetConnectionArtifact(ctx context.Context, account *citypes.Account, req *citypes.ConnectionArtifactRequest) (*citypes.ConnectionArtifact, error)
// ListServicesMetadata returns the list of services metadata for a cloud provider attached with the integrationID.
// This just returns a summary of the service and not the whole service definition
ListServicesMetadata(ctx context.Context, orgID valuer.UUID, integrationID *valuer.UUID) ([]*citypes.ServiceMetadata, error)
// GetService returns service definition details for a serviceID. This returns config and
// other details required to show in service details page on web client.
GetService(ctx context.Context, orgID valuer.UUID, integrationID *valuer.UUID, serviceID string) (*citypes.Service, error)
// UpdateService updates cloud integration service
UpdateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService) error
// AgentCheckIn is called by agent to heartbeat and get latest config in response.
AgentCheckIn(ctx context.Context, orgID valuer.UUID, req *citypes.AgentCheckInRequest) (*citypes.AgentCheckInResponse, error)
// GetDashboardByID returns dashboard JSON for a given dashboard id.
// this only returns the dashboard when the service (embedded in dashboard id) is enabled
// in the org for any cloud integration account
GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error)
// ListDashboards returns list of dashboards across all connected cloud integration accounts
// for enabled services in the org. This list gets added to dashboard list page
ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error)
}
type Handler interface {
GetConnectionArtifact(http.ResponseWriter, *http.Request)
ListAccounts(http.ResponseWriter, *http.Request)
GetAccount(http.ResponseWriter, *http.Request)
UpdateAccount(http.ResponseWriter, *http.Request)
DisconnectAccount(http.ResponseWriter, *http.Request)
ListServicesMetadata(http.ResponseWriter, *http.Request)
GetService(http.ResponseWriter, *http.Request)
UpdateService(http.ResponseWriter, *http.Request)
AgentCheckIn(http.ResponseWriter, *http.Request)
}

View File

@@ -43,7 +43,7 @@ type Module interface {
Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboard, diff int) (*dashboardtypes.Dashboard, error)
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role authtypes.LegacyRole, lock bool) error
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error
Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error

View File

@@ -99,7 +99,7 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
return dashboard, nil
}
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role authtypes.LegacyRole, lock bool) error {
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
dashboard, err := module.Get(ctx, orgID, id)
if err != nil {
return err

View File

@@ -78,7 +78,7 @@ func (m *module) ListPromotedAndIndexedPaths(ctx context.Context) ([]promotetype
// add the paths that are not promoted but have indexes
for path, indexes := range aggr {
path := strings.TrimPrefix(path, telemetrylogs.BodyJSONColumnPrefix)
path := strings.TrimPrefix(path, telemetrylogs.BodyV2ColumnPrefix)
path = telemetrytypes.BodyJSONStringSearchPrefix + path
response = append(response, promotetypes.PromotePath{
Path: path,
@@ -163,7 +163,7 @@ func (m *module) PromoteAndIndexPaths(
}
}
if len(it.Indexes) > 0 {
parentColumn := telemetrylogs.LogsV2BodyJSONColumn
parentColumn := telemetrylogs.LogsV2BodyV2Column
// if the path is already promoted or is being promoted, add it to the promoted column
if _, promoted := existingPromotedPaths[it.Path]; promoted || it.Promote {
parentColumn = telemetrylogs.LogsV2BodyPromotedColumn

View File

@@ -17,7 +17,6 @@ import (
"github.com/SigNoz/signoz/pkg/tokenizer"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -67,7 +66,7 @@ func (module *module) GetSessionContext(ctx context.Context, email valuer.Email,
}
// filter out deleted users
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.ErrIfDeleted() != nil })
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.ErrIfDeleted() != nil })
// Since email is a valuer, we can be sure that it is a valid email and we can split it to get the domain name.
name := strings.Split(email.String(), "@")[1]
@@ -145,7 +144,7 @@ func (module *module) CreateCallbackAuthNSession(ctx context.Context, authNProvi
roleMapping := authDomain.AuthDomainConfig().RoleMapping
role := roleMapping.NewRoleFromCallbackIdentity(callbackIdentity)
user, err := usertypes.NewUser(callbackIdentity.Name, callbackIdentity.Email, role, callbackIdentity.OrgID, usertypes.UserStatusActive)
user, err := types.NewUser(callbackIdentity.Name, callbackIdentity.Email, role, callbackIdentity.OrgID, types.UserStatusActive)
if err != nil {
return "", err
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
"github.com/SigNoz/signoz/pkg/types"
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -31,7 +30,7 @@ func (module *module) Create(ctx context.Context, timestamp int64, name string,
funnel.CreatedBy = userID.String()
// Set up the user relationship
funnel.CreatedByUser = &usertypes.User{
funnel.CreatedByUser = &types.User{
Identifiable: types.Identifiable{
ID: userID,
},

View File

@@ -5,7 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -27,7 +27,12 @@ type OrgConfig struct {
}
type PasswordConfig struct {
Reset ResetConfig `mapstructure:"reset"`
Invite InviteConfig `mapstructure:"invite"`
Reset ResetConfig `mapstructure:"reset"`
}
type InviteConfig struct {
MaxTokenLifetime time.Duration `mapstructure:"max_token_lifetime"`
}
type ResetConfig struct {
@@ -46,6 +51,9 @@ func newConfig() factory.Config {
AllowSelf: false,
MaxTokenLifetime: 6 * time.Hour,
},
Invite: InviteConfig{
MaxTokenLifetime: 48 * time.Hour,
},
},
Root: RootConfig{
Enabled: false,
@@ -61,6 +69,10 @@ func (c Config) Validate() error {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::password::reset::max_token_lifetime must be positive")
}
if c.Password.Invite.MaxTokenLifetime <= 0 {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::password::invite::max_token_lifetime must be positive")
}
if c.Root.Enabled {
if c.Root.Email.IsZero() {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::email is required when root user is enabled")
@@ -68,7 +80,7 @@ func (c Config) Validate() error {
if c.Root.Password == "" {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password is required when root user is enabled")
}
if !usertypes.IsPasswordValid(c.Root.Password) {
if !types.IsPasswordValid(c.Root.Password) {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password does not meet password requirements")
}
}

View File

@@ -6,25 +6,25 @@ import (
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type getter struct {
store usertypes.UserStore
store types.UserStore
flagger flagger.Flagger
}
func NewGetter(store usertypes.UserStore, flagger flagger.Flagger) user.Getter {
func NewGetter(store types.UserStore, flagger flagger.Flagger) user.Getter {
return &getter{store: store, flagger: flagger}
}
func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*usertypes.User, error) {
func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
return module.store.GetRootUserByOrgID(ctx, orgID)
}
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.User, error) {
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.User, error) {
users, err := module.store.ListUsersByOrgID(ctx, orgID)
if err != nil {
return nil, err
@@ -35,13 +35,13 @@ func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*us
hideRootUsers := module.flagger.BooleanOrEmpty(ctx, flagger.FeatureHideRootUser, evalCtx)
if hideRootUsers {
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.IsRoot })
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.IsRoot })
}
return users, nil
}
func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*usertypes.User, error) {
func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
users, err := module.store.GetUsersByEmail(ctx, email)
if err != nil {
return nil, err
@@ -50,7 +50,7 @@ func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) (
return users, nil
}
func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*usertypes.User, error) {
func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
user, err := module.store.GetByOrgIDAndID(ctx, orgID, id)
if err != nil {
return nil, err
@@ -59,7 +59,7 @@ func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id
return user, nil
}
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*usertypes.User, error) {
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.User, error) {
user, err := module.store.GetUser(ctx, id)
if err != nil {
return nil, err
@@ -68,7 +68,7 @@ func (module *getter) Get(ctx context.Context, id valuer.UUID) (*usertypes.User,
return user, nil
}
func (module *getter) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*usertypes.User, error) {
func (module *getter) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
users, err := module.store.ListUsersByEmailAndOrgIDs(ctx, email, orgIDs)
if err != nil {
return nil, err
@@ -95,7 +95,7 @@ func (module *getter) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.
return counts, nil
}
func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valuer.UUID) (*usertypes.FactorPassword, error) {
func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valuer.UUID) (*types.FactorPassword, error) {
factorPassword, err := module.store.GetPasswordByUserID(ctx, userID)
if err != nil {
return nil, err
@@ -103,12 +103,3 @@ func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valu
return factorPassword, nil
}
func (module *getter) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*usertypes.User, *usertypes.FactorPassword, error) {
user, factorPassword, err := module.store.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
if err != nil {
return nil, nil, err
}
return user, factorPassword, nil
}

View File

@@ -11,9 +11,9 @@ import (
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
root "github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
@@ -27,25 +27,6 @@ func NewHandler(module root.Module, getter root.Getter) root.Handler {
return &handler{module: module, getter: getter}
}
func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
req := new(usertypes.PostableAcceptInvite)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(w, err)
return
}
user, err := h.module.AcceptInvite(ctx, req.InviteToken, req.Password)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusCreated, user)
}
func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -56,14 +37,14 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
return
}
var req usertypes.PostableInvite
var req types.PostableInvite
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, err)
return
}
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &usertypes.PostableBulkInviteRequest{
Invites: []usertypes.PostableInvite{req},
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &types.PostableBulkInviteRequest{
Invites: []types.PostableInvite{req},
})
if err != nil {
render.Error(rw, err)
@@ -83,7 +64,7 @@ func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
return
}
var req usertypes.PostableBulkInviteRequest
var req types.PostableBulkInviteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, err)
return
@@ -104,59 +85,6 @@ func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
render.Success(rw, http.StatusCreated, nil)
}
func (h *handler) GetInvite(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
token := mux.Vars(r)["token"]
invite, err := h.module.GetInviteByToken(ctx, token)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, invite)
}
func (h *handler) ListInvite(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
invites, err := h.module.ListInvite(ctx, claims.OrgID)
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusOK, invites)
}
func (h *handler) DeleteInvite(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
id := mux.Vars(r)["id"]
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(w, err)
return
}
if err := h.module.DeleteUser(ctx, valuer.MustNewUUID(claims.OrgID), id, claims.UserID); err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusNoContent, nil)
}
func (h *handler) GetUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
@@ -213,9 +141,6 @@ func (h *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
return
}
// temp code - show only active users
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.Status != usertypes.UserStatusActive })
render.Success(w, http.StatusOK, users)
}
@@ -231,7 +156,7 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
return
}
var user usertypes.User
var user types.User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
render.Error(w, err)
return
@@ -297,7 +222,7 @@ func (handler *handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
req := new(usertypes.PostableResetPassword)
req := new(types.PostableResetPassword)
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
render.Error(w, err)
return
@@ -316,7 +241,7 @@ func (handler *handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
var req usertypes.ChangePasswordRequest
var req types.ChangePasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(w, err)
return
@@ -335,7 +260,7 @@ func (h *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
req := new(usertypes.PostableForgotPassword)
req := new(types.PostableForgotPassword)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(w, err)
return
@@ -360,13 +285,13 @@ func (h *handler) CreateAPIKey(w http.ResponseWriter, r *http.Request) {
return
}
req := new(usertypes.PostableAPIKey)
req := new(types.PostableAPIKey)
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode api key"))
return
}
apiKey, err := usertypes.NewStorableAPIKey(
apiKey, err := types.NewStorableAPIKey(
req.Name,
valuer.MustNewUUID(claims.UserID),
req.Role,
@@ -411,13 +336,13 @@ func (h *handler) ListAPIKeys(w http.ResponseWriter, r *http.Request) {
// for backward compatibility
if len(apiKeys) == 0 {
render.Success(w, http.StatusOK, []usertypes.GettableAPIKey{})
render.Success(w, http.StatusOK, []types.GettableAPIKey{})
return
}
result := make([]*usertypes.GettableAPIKey, len(apiKeys))
result := make([]*types.GettableAPIKey, len(apiKeys))
for i, apiKey := range apiKeys {
result[i] = usertypes.NewGettableAPIKeyFromStorableAPIKey(apiKey)
result[i] = types.NewGettableAPIKeyFromStorableAPIKey(apiKey)
}
render.Success(w, http.StatusOK, result)
@@ -434,7 +359,7 @@ func (h *handler) UpdateAPIKey(w http.ResponseWriter, r *http.Request) {
return
}
req := usertypes.StorableAPIKey{}
req := types.StorableAPIKey{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode api key"))
return

View File

@@ -19,13 +19,12 @@ import (
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/emailtypes"
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/dustin/go-humanize"
)
type Module struct {
store usertypes.UserStore
store types.UserStore
tokenizer tokenizer.Tokenizer
emailing emailing.Emailing
settings factory.ScopedProviderSettings
@@ -36,7 +35,7 @@ type Module struct {
}
// This module is a WIP, don't take inspiration from this.
func NewModule(store usertypes.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, authz authz.AuthZ, analytics analytics.Analytics, config user.Config) root.Module {
func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, authz authz.AuthZ, analytics analytics.Analytics, config user.Config) root.Module {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
return &Module{
store: store,
@@ -50,56 +49,8 @@ func NewModule(store usertypes.UserStore, tokenizer tokenizer.Tokenizer, emailin
}
}
func (m *Module) AcceptInvite(ctx context.Context, token string, password string) (*usertypes.User, error) {
// get the user by reset password token
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
if err != nil {
return nil, err
}
// update the password and delete the token
err = m.UpdatePasswordByResetPasswordToken(ctx, token, password)
if err != nil {
return nil, err
}
// query the user again
user, err = m.store.GetByOrgIDAndID(ctx, user.OrgID, user.ID)
if err != nil {
return nil, err
}
return user, nil
}
func (m *Module) GetInviteByToken(ctx context.Context, token string) (*usertypes.Invite, error) {
// get the user
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
if err != nil {
return nil, err
}
// create a dummy invite obj for backward compatibility
invite := &usertypes.Invite{
Identifiable: types.Identifiable{
ID: user.ID,
},
Name: user.DisplayName,
Email: user.Email,
Token: token,
Role: user.Role,
OrgID: user.OrgID,
TimeAuditable: types.TimeAuditable{
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
},
}
return invite, nil
}
// CreateBulk implements invite.Module.
func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *usertypes.PostableBulkInviteRequest) ([]*usertypes.Invite, error) {
func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.Invite, error) {
creator, err := m.store.GetUser(ctx, userID)
if err != nil {
return nil, err
@@ -110,7 +61,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
for idx, invite := range bulkInvites.Invites {
emails[idx] = invite.Email.StringValue()
}
users, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{usertypes.UserStatusActive.StringValue(), usertypes.UserStatusPendingInvite.StringValue()})
users, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{types.UserStatusActive.StringValue(), types.UserStatusPendingInvite.StringValue()})
if err != nil {
return nil, err
}
@@ -120,7 +71,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
return nil, errors.WithAdditionalf(err, "Cannot send invite to root user")
}
if users[0].Status == usertypes.UserStatusPendingInvite {
if users[0].Status == types.UserStatusPendingInvite {
return nil, errors.Newf(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "An invite already exists for this email: %s", users[0].Email.StringValue())
}
@@ -128,21 +79,21 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
}
type userWithResetToken struct {
User *usertypes.User
ResetPasswordToken *usertypes.ResetPasswordToken
User *types.User
ResetPasswordToken *types.ResetPasswordToken
}
newUsersWithResetToken := make([]*userWithResetToken, len(bulkInvites.Invites))
if err := m.store.RunInTx(ctx, func(ctx context.Context) error {
for idx, invite := range bulkInvites.Invites {
role, err := authtypes.NewLegacyRole(invite.Role.String())
role, err := types.NewRole(invite.Role.String())
if err != nil {
return err
}
// create a new user with pending invite status
newUser, err := usertypes.NewUser(invite.Name, invite.Email, role, orgID, usertypes.UserStatusPendingInvite)
newUser, err := types.NewUser(invite.Name, invite.Email, role, orgID, types.UserStatusPendingInvite)
if err != nil {
return err
}
@@ -170,7 +121,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
return nil, err
}
invites := make([]*usertypes.Invite, len(bulkInvites.Invites))
invites := make([]*types.Invite, len(bulkInvites.Invites))
// send password reset emails to all the invited users
for idx, userWithToken := range newUsersWithResetToken {
@@ -179,7 +130,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
"invitee_role": userWithToken.User.Role,
})
invite := &usertypes.Invite{
invite := &types.Invite{
Identifiable: types.Identifiable{
ID: userWithToken.User.ID,
},
@@ -204,7 +155,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
resetLink := userWithToken.ResetPasswordToken.FactorPasswordResetLink(frontendBaseUrl)
tokenLifetime := m.config.Password.Reset.MaxTokenLifetime
tokenLifetime := m.config.Password.Invite.MaxTokenLifetime
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
if err := m.emailing.SendHTML(ctx, userWithToken.User.Email.String(), "You're Invited to Join SigNoz", emailtypes.TemplateNameInvitationEmail, map[string]any{
@@ -219,47 +170,7 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
return invites, nil
}
func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*usertypes.Invite, error) {
// find all the users with pending_invite status
users, err := m.store.ListUsersByOrgID(ctx, valuer.MustNewUUID(orgID))
if err != nil {
return nil, err
}
pendingUsers := slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.Status != usertypes.UserStatusPendingInvite })
var invites []*usertypes.Invite
for _, pUser := range pendingUsers {
// get the reset password token
resetPasswordToken, err := m.GetOrCreateResetPasswordToken(ctx, pUser.ID)
if err != nil {
return nil, err
}
// create a dummy invite obj for backward compatibility
invite := &usertypes.Invite{
Identifiable: types.Identifiable{
ID: pUser.ID,
},
Name: pUser.DisplayName,
Email: pUser.Email,
Token: resetPasswordToken.Token,
Role: pUser.Role,
OrgID: pUser.OrgID,
TimeAuditable: types.TimeAuditable{
CreatedAt: pUser.CreatedAt,
UpdatedAt: pUser.UpdatedAt, // dummy
},
}
invites = append(invites, invite)
}
return invites, nil
}
func (module *Module) CreateUser(ctx context.Context, input *usertypes.User, opts ...root.CreateUserOption) error {
func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
createUserOpts := root.NewCreateUserOptions(opts...)
// since assign is idempotant multiple calls to assign won't cause issues in case of retries.
@@ -284,14 +195,14 @@ func (module *Module) CreateUser(ctx context.Context, input *usertypes.User, opt
return err
}
traitsOrProperties := usertypes.NewTraitsFromUser(input)
traitsOrProperties := types.NewTraitsFromUser(input)
module.analytics.IdentifyUser(ctx, input.OrgID.String(), input.ID.String(), traitsOrProperties)
module.analytics.TrackUser(ctx, input.OrgID.String(), input.ID.String(), "User Created", traitsOrProperties)
return nil
}
func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *usertypes.User, updatedBy string) (*usertypes.User, error) {
func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error) {
existingUser, err := m.store.GetUser(ctx, valuer.MustNewUUID(id))
if err != nil {
return nil, err
@@ -305,22 +216,18 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
return nil, errors.WithAdditionalf(err, "cannot update deleted user")
}
if err := existingUser.ErrIfPending(); err != nil {
return nil, errors.WithAdditionalf(err, "cannot update pending user")
}
requestor, err := m.store.GetUser(ctx, valuer.MustNewUUID(updatedBy))
if err != nil {
return nil, err
}
if user.Role != "" && user.Role != existingUser.Role && requestor.Role != authtypes.RoleAdmin {
if user.Role != "" && user.Role != existingUser.Role && requestor.Role != types.RoleAdmin {
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles")
}
// Make sure that the request is not demoting the last admin user.
if user.Role != "" && user.Role != existingUser.Role && existingUser.Role == authtypes.RoleAdmin {
adminUsers, err := m.store.GetActiveUsersByRoleAndOrgID(ctx, authtypes.RoleAdmin, orgID)
if user.Role != "" && user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin {
adminUsers, err := m.store.GetActiveUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
if err != nil {
return nil, err
}
@@ -350,12 +257,12 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
return existingUser, nil
}
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error {
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
return err
}
traits := usertypes.NewTraitsFromUser(user)
traits := types.NewTraitsFromUser(user)
module.analytics.IdentifyUser(ctx, user.OrgID.String(), user.ID.String(), traits)
module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Updated", traits)
@@ -385,12 +292,12 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
}
// don't allow to delete the last admin user
adminUsers, err := module.store.GetActiveUsersByRoleAndOrgID(ctx, authtypes.RoleAdmin, orgID)
adminUsers, err := module.store.GetActiveUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
if err != nil {
return err
}
if len(adminUsers) == 1 && user.Role == authtypes.RoleAdmin {
if len(adminUsers) == 1 && user.Role == types.RoleAdmin {
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
}
@@ -412,7 +319,7 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
return nil
}
func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*usertypes.ResetPasswordToken, error) {
func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*types.ResetPasswordToken, error) {
user, err := module.store.GetUser(ctx, userID)
if err != nil {
return nil, err
@@ -435,7 +342,7 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
if password == nil {
// if the user does not have a password, we need to create a new one (common for SSO/SAML users)
password = usertypes.MustGenerateFactorPassword(userID.String())
password = types.MustGenerateFactorPassword(userID.String())
if err := module.store.CreatePassword(ctx, password); err != nil {
return nil, err
@@ -461,7 +368,11 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
}
// create a new token
resetPasswordToken, err := usertypes.NewResetPasswordToken(password.ID, time.Now().Add(module.config.Password.Reset.MaxTokenLifetime))
tokenLifetime := module.config.Password.Reset.MaxTokenLifetime
if user.Status == types.UserStatusPendingInvite {
tokenLifetime = module.config.Password.Invite.MaxTokenLifetime
}
resetPasswordToken, err := types.NewResetPasswordToken(password.ID, time.Now().Add(tokenLifetime))
if err != nil {
return nil, err
}
@@ -501,6 +412,9 @@ func (module *Module) ForgotPassword(ctx context.Context, orgID valuer.UUID, ema
resetLink := token.FactorPasswordResetLink(frontendBaseURL)
tokenLifetime := module.config.Password.Reset.MaxTokenLifetime
if user.Status == types.UserStatusPendingInvite {
tokenLifetime = module.config.Password.Invite.MaxTokenLifetime
}
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
if err := module.emailing.SendHTML(
@@ -554,7 +468,7 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
}
// since grant is idempotent, multiple calls won't cause issues in case of retries
if user.Status == usertypes.UserStatusPendingInvite {
if user.Status == types.UserStatusPendingInvite {
if err = module.authz.Grant(
ctx,
user.OrgID,
@@ -566,8 +480,8 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
}
return module.store.RunInTx(ctx, func(ctx context.Context) error {
if user.Status == usertypes.UserStatusPendingInvite {
if err := user.UpdateStatus(usertypes.UserStatusActive); err != nil {
if user.Status == types.UserStatusPendingInvite {
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
return err
}
if err := module.store.UpdateUser(ctx, user.OrgID, user); err != nil {
@@ -607,7 +521,7 @@ func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, ol
}
if !password.Equals(oldpasswd) {
return errors.New(errors.TypeInvalidInput, usertypes.ErrCodeIncorrectPassword, "old password is incorrect")
return errors.New(errors.TypeInvalidInput, types.ErrCodeIncorrectPassword, "old password is incorrect")
}
if err := password.Update(passwd); err != nil {
@@ -631,7 +545,7 @@ func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, ol
return module.tokenizer.DeleteTokensByUserID(ctx, userID)
}
func (module *Module) GetOrCreateUser(ctx context.Context, user *usertypes.User, opts ...root.CreateUserOption) (*usertypes.User, error) {
func (module *Module) GetOrCreateUser(ctx context.Context, user *types.User, opts ...root.CreateUserOption) (*types.User, error) {
existingUser, err := module.GetNonDeletedUserByEmailAndOrgID(ctx, user.Email, user.OrgID)
if err != nil {
if !errors.Ast(err, errors.TypeNotFound) {
@@ -641,7 +555,7 @@ func (module *Module) GetOrCreateUser(ctx context.Context, user *usertypes.User,
if existingUser != nil {
// for users logging through SSO flow but are having status as pending_invite
if existingUser.Status == usertypes.UserStatusPendingInvite {
if existingUser.Status == types.UserStatusPendingInvite {
// respect the role coming from the SSO
existingUser.Update("", user.Role)
// activate the user
@@ -661,19 +575,19 @@ func (module *Module) GetOrCreateUser(ctx context.Context, user *usertypes.User,
return user, nil
}
func (m *Module) CreateAPIKey(ctx context.Context, apiKey *usertypes.StorableAPIKey) error {
func (m *Module) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
return m.store.CreateAPIKey(ctx, apiKey)
}
func (m *Module) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *usertypes.StorableAPIKey, updaterID valuer.UUID) error {
func (m *Module) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
return m.store.UpdateAPIKey(ctx, id, apiKey, updaterID)
}
func (m *Module) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableAPIKeyUser, error) {
func (m *Module) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
return m.store.ListAPIKeys(ctx, orgID)
}
func (m *Module) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*usertypes.StorableAPIKeyUser, error) {
func (m *Module) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
return m.store.GetAPIKey(ctx, orgID, id)
}
@@ -681,13 +595,13 @@ func (m *Module) RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UU
return m.store.RevokeAPIKey(ctx, id, removedByUserID)
}
func (module *Module) CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, passwd string) (*usertypes.User, error) {
user, err := usertypes.NewRootUser(name, email, organization.ID)
func (module *Module) CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, passwd string) (*types.User, error) {
user, err := types.NewRootUser(name, email, organization.ID)
if err != nil {
return nil, err
}
password, err := usertypes.NewFactorPassword(passwd, user.ID.StringValue())
password, err := types.NewFactorPassword(passwd, user.ID.StringValue())
if err != nil {
return nil, err
}
@@ -726,12 +640,12 @@ func (module *Module) CreateFirstUser(ctx context.Context, organization *types.O
func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
stats := make(map[string]any)
counts, err := module.store.CountByOrgIDAndStatuses(ctx, orgID, []string{usertypes.UserStatusActive.StringValue(), usertypes.UserStatusDeleted.StringValue(), usertypes.UserStatusPendingInvite.StringValue()})
counts, err := module.store.CountByOrgIDAndStatuses(ctx, orgID, []string{types.UserStatusActive.StringValue(), types.UserStatusDeleted.StringValue(), types.UserStatusPendingInvite.StringValue()})
if err == nil {
stats["user.count"] = counts[usertypes.UserStatusActive] + counts[usertypes.UserStatusDeleted] + counts[usertypes.UserStatusPendingInvite]
stats["user.count.active"] = counts[usertypes.UserStatusActive]
stats["user.count.deleted"] = counts[usertypes.UserStatusDeleted]
stats["user.count.pending_invite"] = counts[usertypes.UserStatusPendingInvite]
stats["user.count"] = counts[types.UserStatusActive] + counts[types.UserStatusDeleted] + counts[types.UserStatusPendingInvite]
stats["user.count.active"] = counts[types.UserStatusActive]
stats["user.count.deleted"] = counts[types.UserStatusDeleted]
stats["user.count.pending_invite"] = counts[types.UserStatusPendingInvite]
}
count, err := module.store.CountAPIKeyByOrgID(ctx, orgID)
@@ -743,14 +657,14 @@ func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
}
// this function restricts that only one non-deleted user email can exist for an org ID, if found more, it throws an error
func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*usertypes.User, error) {
func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error) {
existingUsers, err := module.store.GetUsersByEmailAndOrgID(ctx, email, orgID)
if err != nil {
return nil, err
}
// filter out the deleted users
existingUsers = slices.DeleteFunc(existingUsers, func(user *usertypes.User) bool { return user.ErrIfDeleted() != nil })
existingUsers = slices.DeleteFunc(existingUsers, func(user *types.User) bool { return user.ErrIfDeleted() != nil })
if len(existingUsers) > 1 {
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "Multiple non-deleted users found for email %s in org_id: %s", email.StringValue(), orgID.StringValue())
@@ -764,7 +678,7 @@ func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, emai
}
func (module *Module) createUserWithoutGrant(ctx context.Context, input *usertypes.User, opts ...root.CreateUserOption) error {
func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
createUserOpts := root.NewCreateUserOptions(opts...)
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
if err := module.store.CreateUser(ctx, input); err != nil {
@@ -782,14 +696,14 @@ func (module *Module) createUserWithoutGrant(ctx context.Context, input *usertyp
return err
}
traitsOrProperties := usertypes.NewTraitsFromUser(input)
traitsOrProperties := types.NewTraitsFromUser(input)
module.analytics.IdentifyUser(ctx, input.OrgID.String(), input.ID.String(), traitsOrProperties)
module.analytics.TrackUser(ctx, input.OrgID.String(), input.ID.String(), "User Created", traitsOrProperties)
return nil
}
func (module *Module) activatePendingUser(ctx context.Context, user *usertypes.User) error {
func (module *Module) activatePendingUser(ctx context.Context, user *types.User) error {
err := module.authz.Grant(
ctx,
user.OrgID,
@@ -800,7 +714,7 @@ func (module *Module) activatePendingUser(ctx context.Context, user *usertypes.U
return err
}
if err := user.UpdateStatus(usertypes.UserStatusActive); err != nil {
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
return err
}
err = module.store.UpdateUser(ctx, user.OrgID, user)

View File

@@ -11,13 +11,12 @@ import (
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type service struct {
settings factory.ScopedProviderSettings
store usertypes.UserStore
store types.UserStore
module user.Module
orgGetter organization.Getter
authz authz.AuthZ
@@ -27,7 +26,7 @@ type service struct {
func NewService(
providerSettings factory.ProviderSettings,
store usertypes.UserStore,
store types.UserStore,
module user.Module,
orgGetter organization.Getter,
authz authz.AuthZ,
@@ -156,11 +155,11 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
return err
}
if oldRole != authtypes.RoleAdmin {
if oldRole != types.RoleAdmin {
if err := s.authz.ModifyGrant(ctx,
orgID,
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(oldRole)},
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(authtypes.RoleAdmin)},
[]string{authtypes.MustGetSigNozManagedRoleFromExistingRole(types.RoleAdmin)},
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), orgID, nil),
); err != nil {
return err
@@ -171,12 +170,12 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
}
// Create new root user
newUser, err := usertypes.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
newUser, err := types.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
if err != nil {
return err
}
factorPassword, err := usertypes.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
factorPassword, err := types.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
if err != nil {
return err
}
@@ -184,7 +183,7 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
return s.module.CreateUser(ctx, newUser, user.WithFactorPassword(factorPassword))
}
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *usertypes.User) error {
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *types.User) error {
existingRoot.PromoteToRoot()
if existingRoot.Email != s.config.Email {
@@ -204,7 +203,7 @@ func (s *service) setPassword(ctx context.Context, userID valuer.UUID) error {
return err
}
factorPassword, err := usertypes.NewFactorPassword(s.config.Password, userID.StringValue())
factorPassword, err := types.NewFactorPassword(s.config.Password, userID.StringValue())
if err != nil {
return err
}

View File

@@ -9,9 +9,9 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
@@ -21,11 +21,11 @@ type store struct {
settings factory.ProviderSettings
}
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) usertypes.UserStore {
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) types.UserStore {
return &store{sqlstore: sqlstore, settings: settings}
}
func (store *store) CreatePassword(ctx context.Context, password *usertypes.FactorPassword) error {
func (store *store) CreatePassword(ctx context.Context, password *types.FactorPassword) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -33,13 +33,13 @@ func (store *store) CreatePassword(ctx context.Context, password *usertypes.Fact
Model(password).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrPasswordAlreadyExists, "password for user %s already exists", password.UserID)
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password for user %s already exists", password.UserID)
}
return nil
}
func (store *store) CreateUser(ctx context.Context, user *usertypes.User) error {
func (store *store) CreateUser(ctx context.Context, user *types.User) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -47,13 +47,13 @@ func (store *store) CreateUser(ctx context.Context, user *usertypes.User) error
Model(user).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrUserAlreadyExists, "user with email %s already exists in org %s", user.Email, user.OrgID)
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email %s already exists in org %s", user.Email, user.OrgID)
}
return nil
}
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*usertypes.User, error) {
var users []*usertypes.User
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
var users []*types.User
err := store.
sqlstore.
@@ -69,8 +69,8 @@ func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]
return users, nil
}
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*usertypes.User, error) {
user := new(usertypes.User)
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.User, error) {
user := new(types.User)
err := store.
sqlstore.
@@ -80,14 +80,14 @@ func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*usertypes.Use
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with id %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
}
return user, nil
}
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*usertypes.User, error) {
user := new(usertypes.User)
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
user := new(types.User)
err := store.
sqlstore.
@@ -98,14 +98,14 @@ func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id v
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with id %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
}
return user, nil
}
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*usertypes.User, error) {
var users []*usertypes.User
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*types.User, error) {
var users []*types.User
err := store.
sqlstore.
@@ -122,8 +122,8 @@ func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Em
return users, nil
}
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role authtypes.LegacyRole, orgID valuer.UUID) ([]*usertypes.User, error) {
var users []*usertypes.User
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types.Role, orgID valuer.UUID) ([]*types.User, error) {
var users []*types.User
err := store.
sqlstore.
@@ -132,7 +132,7 @@ func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role autht
Model(&users).
Where("org_id = ?", orgID).
Where("role = ?", role).
Where("status = ?", usertypes.UserStatusActive.StringValue()).
Where("status = ?", types.UserStatusActive.StringValue()).
Scan(ctx)
if err != nil {
return nil, err
@@ -141,7 +141,7 @@ func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role autht
return users, nil
}
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error {
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -157,13 +157,13 @@ func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *use
Where("id = ?", user.ID).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
return store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
}
return nil
}
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.GettableUser, error) {
users := []*usertypes.User{}
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.GettableUser, error) {
users := []*types.User{}
err := store.
sqlstore.
@@ -191,7 +191,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// get the password id
var password usertypes.FactorPassword
var password types.FactorPassword
err = tx.NewSelect().
Model(&password).
Where("user_id = ?", id).
@@ -202,7 +202,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// delete reset password request
_, err = tx.NewDelete().
Model(new(usertypes.ResetPasswordToken)).
Model(new(types.ResetPasswordToken)).
Where("password_id = ?", password.ID.String()).
Exec(ctx)
if err != nil {
@@ -211,7 +211,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// delete factor password
_, err = tx.NewDelete().
Model(new(usertypes.FactorPassword)).
Model(new(types.FactorPassword)).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
@@ -220,7 +220,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// delete api keys
_, err = tx.NewDelete().
Model(&usertypes.StorableAPIKey{}).
Model(&types.StorableAPIKey{}).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
@@ -247,7 +247,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// delete user
_, err = tx.NewDelete().
Model(new(usertypes.User)).
Model(new(types.User)).
Where("org_id = ?", orgID).
Where("id = ?", id).
Exec(ctx)
@@ -275,7 +275,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// get the password id
var password usertypes.FactorPassword
var password types.FactorPassword
err = tx.NewSelect().
Model(&password).
Where("user_id = ?", id).
@@ -286,7 +286,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// delete reset password request
_, err = tx.NewDelete().
Model(new(usertypes.ResetPasswordToken)).
Model(new(types.ResetPasswordToken)).
Where("password_id = ?", password.ID.String()).
Exec(ctx)
if err != nil {
@@ -295,7 +295,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// delete factor password
_, err = tx.NewDelete().
Model(new(usertypes.FactorPassword)).
Model(new(types.FactorPassword)).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
@@ -304,7 +304,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// delete api keys
_, err = tx.NewDelete().
Model(&usertypes.StorableAPIKey{}).
Model(&types.StorableAPIKey{}).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
@@ -332,8 +332,8 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// soft delete user
now := time.Now()
_, err = tx.NewUpdate().
Model(new(usertypes.User)).
Set("status = ?", usertypes.UserStatusDeleted).
Model(new(types.User)).
Set("status = ?", types.UserStatusDeleted).
Set("deleted_at = ?", now).
Set("updated_at = ?", now).
Where("org_id = ?", orgID).
@@ -351,7 +351,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
return nil
}
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordToken *usertypes.ResetPasswordToken) error {
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordToken *types.ResetPasswordToken) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -359,14 +359,14 @@ func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordT
Model(resetPasswordToken).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrResetPasswordTokenAlreadyExists, "reset password token for password %s already exists", resetPasswordToken.PasswordID)
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrResetPasswordTokenAlreadyExists, "reset password token for password %s already exists", resetPasswordToken.PasswordID)
}
return nil
}
func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*usertypes.FactorPassword, error) {
password := new(usertypes.FactorPassword)
func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*types.FactorPassword, error) {
password := new(types.FactorPassword)
err := store.
sqlstore.
@@ -376,14 +376,14 @@ func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*usertypes
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "password with id: %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with id: %s does not exist", id)
}
return password, nil
}
func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*usertypes.FactorPassword, error) {
password := new(usertypes.FactorPassword)
func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*types.FactorPassword, error) {
password := new(types.FactorPassword)
err := store.
sqlstore.
@@ -393,13 +393,13 @@ func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID)
Where("user_id = ?", userID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "password for user %s does not exist", userID)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", userID)
}
return password, nil
}
func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*usertypes.ResetPasswordToken, error) {
resetPasswordToken := new(usertypes.ResetPasswordToken)
func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*types.ResetPasswordToken, error) {
resetPasswordToken := new(types.ResetPasswordToken)
err := store.
sqlstore.
@@ -409,7 +409,7 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
Where("password_id = ?", passwordID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrResetPasswordTokenNotFound, "reset password token for password %s does not exist", passwordID)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token for password %s does not exist", passwordID)
}
return resetPasswordToken, nil
@@ -417,7 +417,7 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) error {
_, err := store.sqlstore.BunDBCtx(ctx).NewDelete().
Model(&usertypes.ResetPasswordToken{}).
Model(&types.ResetPasswordToken{}).
Where("password_id = ?", passwordID).
Exec(ctx)
if err != nil {
@@ -427,8 +427,8 @@ func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, pa
return nil
}
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*usertypes.ResetPasswordToken, error) {
resetPasswordRequest := new(usertypes.ResetPasswordToken)
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*types.ResetPasswordToken, error) {
resetPasswordRequest := new(types.ResetPasswordToken)
err := store.
sqlstore.
@@ -438,38 +438,38 @@ func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*u
Where("token = ?", token).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrResetPasswordTokenNotFound, "reset password token does not exist")
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token does not exist")
}
return resetPasswordRequest, nil
}
func (store *store) UpdatePassword(ctx context.Context, factorPassword *usertypes.FactorPassword) error {
func (store *store) UpdatePassword(ctx context.Context, factorPassword *types.FactorPassword) error {
_, err := store.sqlstore.BunDBCtx(ctx).
NewUpdate().
Model(factorPassword).
Where("user_id = ?", factorPassword.UserID).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "password for user %s does not exist", factorPassword.UserID)
return store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", factorPassword.UserID)
}
return nil
}
// --- API KEY ---
func (store *store) CreateAPIKey(ctx context.Context, apiKey *usertypes.StorableAPIKey) error {
func (store *store) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
_, err := store.sqlstore.BunDB().NewInsert().
Model(apiKey).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
}
return nil
}
func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *usertypes.StorableAPIKey, updaterID valuer.UUID) error {
func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
apiKey.UpdatedBy = updaterID.String()
apiKey.UpdatedAt = time.Now()
_, err := store.sqlstore.BunDB().NewUpdate().
@@ -479,13 +479,13 @@ func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *us
Where("revoked = false").
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
return store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
}
return nil
}
func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableAPIKeyUser, error) {
orgUserAPIKeys := new(usertypes.OrgUserAPIKey)
func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
orgUserAPIKeys := new(types.OrgUserAPIKey)
if err := store.sqlstore.BunDB().NewSelect().
Model(orgUserAPIKeys).
@@ -502,7 +502,7 @@ func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*user
}
// Flatten the API keys from all users
var allAPIKeys []*usertypes.StorableAPIKeyUser
var allAPIKeys []*types.StorableAPIKeyUser
for _, user := range orgUserAPIKeys.Users {
if user.APIKeys != nil {
allAPIKeys = append(allAPIKeys, user.APIKeys...)
@@ -520,7 +520,7 @@ func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*user
func (store *store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUID) error {
updatedAt := time.Now().Unix()
_, err := store.sqlstore.BunDB().NewUpdate().
Model(&usertypes.StorableAPIKey{}).
Model(&types.StorableAPIKey{}).
Set("revoked = ?", true).
Set("updated_by = ?", revokedByUserID).
Set("updated_at = ?", updatedAt).
@@ -532,8 +532,8 @@ func (store *store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer
return nil
}
func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*usertypes.StorableAPIKeyUser, error) {
apiKey := new(usertypes.OrgUserAPIKey)
func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
apiKey := new(types.OrgUserAPIKey)
if err := store.sqlstore.BunDB().NewSelect().
Model(apiKey).
Relation("Users").
@@ -545,25 +545,25 @@ func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*user
Relation("Users.APIKeys.CreatedByUser").
Relation("Users.APIKeys.UpdatedByUser").
Scan(ctx); err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
}
// flatten the API keys
flattenedAPIKeys := []*usertypes.StorableAPIKeyUser{}
flattenedAPIKeys := []*types.StorableAPIKeyUser{}
for _, user := range apiKey.Users {
if user.APIKeys != nil {
flattenedAPIKeys = append(flattenedAPIKeys, user.APIKeys...)
}
}
if len(flattenedAPIKeys) == 0 {
return nil, store.sqlstore.WrapNotFoundErrf(errors.New(errors.TypeNotFound, errors.CodeNotFound, "API key with id: %s does not exist"), usertypes.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(errors.New(errors.TypeNotFound, errors.CodeNotFound, "API key with id: %s does not exist"), types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
}
return flattenedAPIKeys[0], nil
}
func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
user := new(usertypes.User)
user := new(types.User)
count, err := store.
sqlstore.
@@ -580,7 +580,7 @@ func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64,
}
func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, statuses []string) (map[valuer.String]int64, error) {
user := new(usertypes.User)
user := new(types.User)
var results []struct {
Status valuer.String `bun:"status"`
Count int64 `bun:"count"`
@@ -610,7 +610,7 @@ func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UU
}
func (store *store) CountAPIKeyByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
apiKey := new(usertypes.StorableAPIKey)
apiKey := new(types.StorableAPIKey)
count, err := store.
sqlstore.
@@ -633,8 +633,8 @@ func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) er
})
}
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*usertypes.User, error) {
user := new(usertypes.User)
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
user := new(types.User)
err := store.
sqlstore.
BunDBCtx(ctx).
@@ -644,13 +644,13 @@ func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (
Where("is_root = ?", true).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "root user for org %s not found", orgID)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "root user for org %s not found", orgID)
}
return user, nil
}
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*usertypes.User, error) {
users := []*usertypes.User{}
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
users := []*types.User{}
err := store.
sqlstore.
BunDB().
@@ -666,8 +666,8 @@ func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.
return users, nil
}
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*usertypes.User, error) {
user := new(usertypes.User)
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*types.User, error) {
user := new(types.User)
err := store.
sqlstore.
@@ -679,14 +679,14 @@ func (store *store) GetUserByResetPasswordToken(ctx context.Context, token strin
Where("reset_password_token.token = ?", token).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user not found for reset password token")
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user not found for reset password token")
}
return user, nil
}
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*usertypes.User, error) {
users := []*usertypes.User{}
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*types.User, error) {
users := []*types.User{}
err := store.
sqlstore.
@@ -703,34 +703,3 @@ func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID
return users, nil
}
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*usertypes.User, *usertypes.FactorPassword, error) {
user := new(usertypes.User)
factorPassword := new(usertypes.FactorPassword)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(user).
Where("email = ?", email).
Where("org_id = ?", orgID).
Where("status = ?", usertypes.UserStatusActive.StringValue()).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with email %s in org %s not found", email, orgID)
}
err = store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(factorPassword).
Where("user_id = ?", user.ID).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "user with email %s in org %s does not have password", email, orgID)
}
return user, factorPassword, nil
}

View File

@@ -1,17 +1,17 @@
package user
import (
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
type createUserOptions struct {
FactorPassword *usertypes.FactorPassword
FactorPassword *types.FactorPassword
}
type CreateUserOption func(*createUserOptions)
func WithFactorPassword(factorPassword *usertypes.FactorPassword) CreateUserOption {
func WithFactorPassword(factorPassword *types.FactorPassword) CreateUserOption {
return func(o *createUserOptions) {
o.FactorPassword = factorPassword
}

View File

@@ -6,23 +6,22 @@ import (
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
// Creates the organization and the first user of that organization.
CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, password string) (*usertypes.User, error)
CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, password string) (*types.User, error)
// Creates a user and sends an analytics event.
CreateUser(ctx context.Context, user *usertypes.User, opts ...CreateUserOption) error
CreateUser(ctx context.Context, user *types.User, opts ...CreateUserOption) error
// Get or create a user. If a user with the same email and orgID already exists, it returns the existing user.
GetOrCreateUser(ctx context.Context, user *usertypes.User, opts ...CreateUserOption) (*usertypes.User, error)
GetOrCreateUser(ctx context.Context, user *types.User, opts ...CreateUserOption) (*types.User, error)
// Get or Create a reset password token for a user. If the password does not exist, a new one is randomly generated and inserted. The function
// is idempotent and can be called multiple times.
GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*usertypes.ResetPasswordToken, error)
GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*types.ResetPasswordToken, error)
// Updates password of a user using a reset password token. It also deletes all reset password tokens for the user.
// This is used to reset the password of a user when they forget their password.
@@ -34,48 +33,45 @@ type Module interface {
// Initiate forgot password flow for a user
ForgotPassword(ctx context.Context, orgID valuer.UUID, email valuer.Email, frontendBaseURL string) error
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *usertypes.User, updatedBy string) (*usertypes.User, error)
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error)
// UpdateAnyUser updates a user and persists the changes to the database along with the analytics and identity deletion.
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error
DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error
// invite
CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *usertypes.PostableBulkInviteRequest) ([]*usertypes.Invite, error)
ListInvite(ctx context.Context, orgID string) ([]*usertypes.Invite, error)
AcceptInvite(ctx context.Context, token string, password string) (*usertypes.User, error)
GetInviteByToken(ctx context.Context, token string) (*usertypes.Invite, error)
CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.Invite, error)
// API KEY
CreateAPIKey(ctx context.Context, apiKey *usertypes.StorableAPIKey) error
UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *usertypes.StorableAPIKey, updaterID valuer.UUID) error
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableAPIKeyUser, error)
CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error
UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error)
RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UUID) error
GetAPIKey(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*usertypes.StorableAPIKeyUser, error)
GetAPIKey(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.StorableAPIKeyUser, error)
GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*usertypes.User, error)
GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error)
statsreporter.StatsCollector
}
type Getter interface {
// Get root user by org id.
GetRootUserByOrgID(context.Context, valuer.UUID) (*usertypes.User, error)
GetRootUserByOrgID(context.Context, valuer.UUID) (*types.User, error)
// Get gets the users based on the given id
ListByOrgID(context.Context, valuer.UUID) ([]*usertypes.User, error)
ListByOrgID(context.Context, valuer.UUID) ([]*types.User, error)
// Get users by email.
GetUsersByEmail(context.Context, valuer.Email) ([]*usertypes.User, error)
GetUsersByEmail(context.Context, valuer.Email) ([]*types.User, error)
// Get user by orgID and id.
GetByOrgIDAndID(context.Context, valuer.UUID, valuer.UUID) (*usertypes.User, error)
GetByOrgIDAndID(context.Context, valuer.UUID, valuer.UUID) (*types.User, error)
// Get user by id.
Get(context.Context, valuer.UUID) (*usertypes.User, error)
Get(context.Context, valuer.UUID) (*types.User, error)
// List users by email and org ids.
ListUsersByEmailAndOrgIDs(context.Context, valuer.Email, []valuer.UUID) ([]*usertypes.User, error)
ListUsersByEmailAndOrgIDs(context.Context, valuer.Email, []valuer.UUID) ([]*types.User, error)
// Count users by org id.
CountByOrgID(context.Context, valuer.UUID) (int64, error)
@@ -84,19 +80,12 @@ type Getter interface {
CountByOrgIDAndStatuses(context.Context, valuer.UUID, []string) (map[valuer.String]int64, error)
// Get factor password by user id.
GetFactorPasswordByUserID(context.Context, valuer.UUID) (*usertypes.FactorPassword, error)
// Get Active User and FactorPassword by email and org id.
GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*usertypes.User, *usertypes.FactorPassword, error)
GetFactorPasswordByUserID(context.Context, valuer.UUID) (*types.FactorPassword, error)
}
type Handler interface {
// invite
CreateInvite(http.ResponseWriter, *http.Request)
AcceptInvite(http.ResponseWriter, *http.Request)
GetInvite(http.ResponseWriter, *http.Request) // public function
ListInvite(http.ResponseWriter, *http.Request)
DeleteInvite(http.ResponseWriter, *http.Request)
CreateBulkInvite(http.ResponseWriter, *http.Request)
ListUsers(http.ResponseWriter, *http.Request)

View File

@@ -10,13 +10,11 @@ import (
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/telemetrylogs"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/bytedance/sonic"
)
type builderQuery[T any] struct {
@@ -262,40 +260,6 @@ func (q *builderQuery[T]) executeWithContext(ctx context.Context, query string,
return nil, err
}
// merge body_json and promoted into body
if q.spec.Signal == telemetrytypes.SignalLogs {
switch typedPayload := payload.(type) {
case *qbtypes.RawData:
for _, rr := range typedPayload.Rows {
seeder := func() error {
body, ok := rr.Data[telemetrylogs.LogsV2BodyJSONColumn].(map[string]any)
if !ok {
return nil
}
promoted, ok := rr.Data[telemetrylogs.LogsV2BodyPromotedColumn].(map[string]any)
if !ok {
return nil
}
seed(promoted, body)
str, err := sonic.MarshalString(body)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to marshal body")
}
rr.Data["body"] = str
return nil
}
err := seeder()
if err != nil {
return nil, err
}
delete(rr.Data, telemetrylogs.LogsV2BodyJSONColumn)
delete(rr.Data, telemetrylogs.LogsV2BodyPromotedColumn)
}
payload = typedPayload
}
}
return &qbtypes.Result{
Type: q.kind,
Value: payload,
@@ -423,18 +387,3 @@ func decodeCursor(cur string) (int64, error) {
}
return strconv.ParseInt(string(b), 10, 64)
}
func seed(promoted map[string]any, body map[string]any) {
for key, fromValue := range promoted {
if toValue, ok := body[key]; !ok {
body[key] = fromValue
} else {
if fromValue, ok := fromValue.(map[string]any); ok {
if toValue, ok := toValue.(map[string]any); ok {
seed(fromValue, toValue)
body[key] = toValue
}
}
}
}
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/bytedance/sonic"
)
var (
@@ -394,17 +393,11 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) {
// de-reference the typed pointer to any
val := reflect.ValueOf(cellPtr).Elem().Interface()
// Post-process JSON columns: normalize into structured values
// Post-process JSON columns: normalize into String value
if strings.HasPrefix(strings.ToUpper(colTypes[i].DatabaseTypeName()), "JSON") {
switch x := val.(type) {
case []byte:
if len(x) > 0 {
var v any
if err := sonic.Unmarshal(x, &v); err == nil {
val = v
}
}
val = string(x)
default:
// already a structured type (map[string]any, []any, etc.)
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/dustin/go-humanize"
"golang.org/x/exp/maps"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
@@ -158,7 +159,8 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
metricNames := make([]string, 0)
for idx, query := range req.CompositeQuery.Queries {
event.QueryType = query.Type.StringValue()
if query.Type == qbtypes.QueryTypeBuilder {
switch query.Type {
case qbtypes.QueryTypeBuilder:
if spec, ok := query.Spec.(qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]); ok {
for _, agg := range spec.Aggregations {
if agg.MetricName != "" {
@@ -236,7 +238,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
req.CompositeQuery.Queries[idx].Spec = spec
}
} else if query.Type == qbtypes.QueryTypePromQL {
case qbtypes.QueryTypePromQL:
event.MetricsUsed = true
switch spec := query.Spec.(type) {
case qbtypes.PromQuery:
@@ -247,7 +249,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
req.CompositeQuery.Queries[idx].Spec = spec
}
} else if query.Type == qbtypes.QueryTypeClickHouseSQL {
case qbtypes.QueryTypeClickHouseSQL:
switch spec := query.Spec.(type) {
case qbtypes.ClickHouseQuery:
if strings.TrimSpace(spec.Query) != "" {
@@ -256,7 +258,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
event.TracesUsed = strings.Contains(spec.Query, "signoz_traces")
}
}
} else if query.Type == qbtypes.QueryTypeTraceOperator {
case qbtypes.QueryTypeTraceOperator:
if spec, ok := query.Spec.(qbtypes.QueryBuilderTraceOperator); ok {
if spec.StepInterval.Seconds() == 0 {
spec.StepInterval = qbtypes.Step{
@@ -276,23 +278,9 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
}
// Fetch temporality for all metrics at once
var metricTemporality map[string]metrictypes.Temporality
var metricTypes map[string]metrictypes.Type
if len(metricNames) > 0 {
var err error
metricTemporality, metricTypes, err = q.metadataStore.FetchTemporalityAndTypeMulti(ctx, req.Start, req.End, metricNames...)
if err != nil {
q.logger.WarnContext(ctx, "failed to fetch metric temporality", "error", err, "metrics", metricNames)
// Continue without temporality - statement builder will handle unspecified
metricTemporality = make(map[string]metrictypes.Temporality)
metricTypes = make(map[string]metrictypes.Type)
}
q.logger.DebugContext(ctx, "fetched metric temporalities and types", "metric_temporality", metricTemporality, "metric_types", metricTypes)
}
queries := make(map[string]qbtypes.Query)
steps := make(map[string]qbtypes.Step)
missingMetrics := []string{}
for _, query := range req.CompositeQuery.Queries {
var queryName string
@@ -374,15 +362,26 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
queries[spec.Name] = bq
steps[spec.Name] = spec.StepInterval
case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]:
var metricTemporality map[string]metrictypes.Temporality
var metricTypes map[string]metrictypes.Type
if len(metricNames) > 0 {
var err error
metricTemporality, metricTypes, err = q.metadataStore.FetchTemporalityAndTypeMulti(ctx, req.Start, req.End, metricNames...)
if err != nil {
q.logger.WarnContext(ctx, "failed to fetch metric temporality", "error", err, "metrics", metricNames)
return nil, errors.NewInternalf(errors.CodeInternal, "failed to fetch metrics temporality")
}
q.logger.DebugContext(ctx, "fetched metric temporalities and types", "metric_temporality", metricTemporality, "metric_types", metricTypes)
}
for i := range spec.Aggregations {
if spec.Aggregations[i].MetricName != "" && spec.Aggregations[i].Temporality == metrictypes.Unknown {
if temp, ok := metricTemporality[spec.Aggregations[i].MetricName]; ok && temp != metrictypes.Unknown {
spec.Aggregations[i].Temporality = temp
}
}
// TODO(srikanthccv): warn when the metric is missing
if spec.Aggregations[i].Temporality == metrictypes.Unknown {
spec.Aggregations[i].Temporality = metrictypes.Unspecified
missingMetrics = append(missingMetrics, spec.Aggregations[i].MetricName)
continue
}
if spec.Aggregations[i].MetricName != "" && spec.Aggregations[i].Type == metrictypes.UnspecifiedType {
@@ -409,6 +408,24 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
}
}
if len(missingMetrics) > 0 {
lastSeenInfo, _ := q.metadataStore.FetchLastSeenInfoMulti(ctx, missingMetrics...)
lastSeenStr := func(name string) string {
if ts, ok := lastSeenInfo[name]; ok && ts > 0 {
ago := humanize.RelTime(time.UnixMilli(ts), time.Now(), "ago", "from now")
return fmt.Sprintf("%s (last seen %s)", name, ago)
}
return name
}
if len(missingMetrics) == 1 {
return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the metric %s in the query time range", lastSeenStr(missingMetrics[0]))
}
parts := make([]string, len(missingMetrics))
for i, m := range missingMetrics {
parts[i] = lastSeenStr(m)
}
return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the following metrics in the query time range: %s", strings.Join(parts, ", "))
}
qbResp, qbErr := q.run(ctx, orgID, queries, req, steps, event)
if qbResp != nil {
qbResp.QBEvent = event
@@ -663,7 +680,7 @@ func (q *querier) run(
}
// executeWithCache executes a query using the bucket cache
func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query qbtypes.Query, step qbtypes.Step, noCache bool) (*qbtypes.Result, error) {
func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query qbtypes.Query, step qbtypes.Step, _ bool) (*qbtypes.Result, error) {
// Get cached data and missing ranges
cachedResult, missingRanges := q.bucketCache.GetMissRanges(ctx, orgID, query, step)

View File

@@ -73,7 +73,6 @@ import (
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
@@ -2035,7 +2034,7 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return
}
var req usertypes.PostableRegisterOrgAndAdmin
var req types.PostableRegisterOrgAndAdmin
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(w, err)
return

View File

@@ -28,9 +28,6 @@ const SpanSearchScopeRoot = "isroot"
const SpanSearchScopeEntryPoint = "isentrypoint"
const OrderBySpanCount = "span_count"
// Deprecated: Use the new emailing service instead
var InviteEmailTemplate = GetOrDefaultEnv("INVITE_EMAIL_TEMPLATE", "/root/templates/invitation_email.gotmpl")
var MetricsExplorerClickhouseThreads = GetOrDefaultEnvInt("METRICS_EXPLORER_CLICKHOUSE_THREADS", 8)
var UpdatedMetricsMetadataCachePrefix = GetOrDefaultEnv("METRICS_UPDATED_METADATA_CACHE_KEY", "UPDATED_METRICS_METADATA")

View File

@@ -76,6 +76,21 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
alertDataRows := cmock.NewRows(cols, tc.Values)
mock := mockStore.Mock()
// Mock metadata queries for FetchTemporalityAndTypeMulti
// First query: fetchMetricsTemporalityAndType (from signoz_metrics time series table)
metadataCols := []cmock.ColumnType{
{Name: "metric_name", Type: "String"},
{Name: "temporality", Type: "String"},
{Name: "type", Type: "String"},
{Name: "is_monotonic", Type: "Bool"},
}
metadataRows := cmock.NewRows(metadataCols, [][]any{
{"probe_success", metrictypes.Unspecified, metrictypes.GaugeType, false},
})
mock.ExpectQuery("*distributed_time_series_v4*").WithArgs(nil, nil, nil).WillReturnRows(metadataRows)
// Second query: fetchMeterSourceMetricsTemporalityAndType (from signoz_meter table)
emptyMetadataRows := cmock.NewRows(metadataCols, [][]any{})
mock.ExpectQuery("*meter*").WithArgs(nil).WillReturnRows(emptyMetadataRows)
// Generate query arguments for the metric query
evalTime := time.Now().UTC()

View File

@@ -7,12 +7,14 @@ import (
"sync"
"time"
"log/slog"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
opentracing "github.com/opentracing/opentracing-go"
plabels "github.com/prometheus/prometheus/model/labels"
"log/slog"
)
// PromRuleTask is a promql rule executor
@@ -371,7 +373,7 @@ func (g *PromRuleTask) Eval(ctx context.Context, ts time.Time) {
comment := ctxtypes.CommentFromContext(ctx)
comment.Set("rule_id", rule.ID())
comment.Set("auth_type", "internal")
comment.Set("identn_provider", authtypes.IdentNProviderInternal.StringValue())
ctx = ctxtypes.NewContextWithComment(ctx, comment)
_, err := rule.Eval(ctx, ts)

View File

@@ -10,6 +10,7 @@ import (
"log/slog"
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -358,7 +359,7 @@ func (g *RuleTask) Eval(ctx context.Context, ts time.Time) {
comment := ctxtypes.CommentFromContext(ctx)
comment.Set("rule_id", rule.ID())
comment.Set("auth_type", "internal")
comment.Set("identn_provider", authtypes.IdentNProviderInternal.StringValue())
ctx = ctxtypes.NewContextWithComment(ctx, comment)
_, err := rule.Eval(ctx, ts)

View File

@@ -219,7 +219,6 @@ func DataTypeCollisionHandledFieldName(key *telemetrytypes.TelemetryFieldKey, va
// we don't have a toBoolOrNull in ClickHouse, so we need to convert the bool to a string
value = fmt.Sprintf("%t", v)
}
case telemetrytypes.FieldDataTypeInt64,
telemetrytypes.FieldDataTypeArrayInt64,
telemetrytypes.FieldDataTypeNumber,

View File

@@ -313,37 +313,30 @@ func (v *filterExpressionVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any
return ""
}
child := ctx.GetChild(0)
var searchText string
if keyCtx, ok := child.(*grammar.KeyContext); ok {
// create a full text search condition on the body field
keyText := keyCtx.GetText()
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(keyText), v.builder, v.startNs, v.endNs)
if err != nil {
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
return ""
}
return cond
searchText = keyCtx.GetText()
} else if valCtx, ok := child.(*grammar.ValueContext); ok {
var text string
if valCtx.QUOTED_TEXT() != nil {
text = trimQuotes(valCtx.QUOTED_TEXT().GetText())
searchText = trimQuotes(valCtx.QUOTED_TEXT().GetText())
} else if valCtx.NUMBER() != nil {
text = valCtx.NUMBER().GetText()
searchText = valCtx.NUMBER().GetText()
} else if valCtx.BOOL() != nil {
text = valCtx.BOOL().GetText()
searchText = valCtx.BOOL().GetText()
} else if valCtx.KEY() != nil {
text = valCtx.KEY().GetText()
searchText = valCtx.KEY().GetText()
} else {
v.errors = append(v.errors, fmt.Sprintf("unsupported value type: %s", valCtx.GetText()))
return ""
}
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder, v.startNs, v.endNs)
if err != nil {
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
return ""
}
return cond
}
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(searchText), v.builder, v.startNs, v.endNs)
if err != nil {
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
return ""
}
return cond
}
return "" // Should not happen with valid input
@@ -383,6 +376,7 @@ func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext
for _, key := range keys {
condition, err := v.conditionBuilder.ConditionFor(context.Background(), key, op, nil, v.builder, v.startNs, v.endNs)
if err != nil {
v.errors = append(v.errors, fmt.Sprintf("failed to build condition: %s", err.Error()))
return ""
}
conds = append(conds, condition)
@@ -648,7 +642,6 @@ func (v *filterExpressionVisitor) VisitValueList(ctx *grammar.ValueListContext)
// VisitFullText handles standalone quoted strings for full-text search
func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) any {
if v.skipFullTextFilter {
return ""
}
@@ -670,6 +663,7 @@ func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) an
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
return ""
}
return cond
}

View File

@@ -8,12 +8,11 @@ import (
"github.com/SigNoz/signoz/pkg/authn/passwordauthn/emailpasswordauthn"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types/authtypes"
)
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userGetter user.Getter) (map[authtypes.AuthNProvider]authn.AuthN, error) {
emailPasswordAuthN := emailpasswordauthn.New(userGetter)
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
emailPasswordAuthN := emailpasswordauthn.New(store)
googleCallbackAuthN, err := googlecallbackauthn.New(ctx, store, providerSettings)
if err != nil {

View File

@@ -20,6 +20,7 @@ import (
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/identn"
"github.com/SigNoz/signoz/pkg/instrumentation"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/user"
@@ -113,6 +114,9 @@ type Config struct {
// User config
User user.Config `mapstructure:"user"`
// IdentN config
IdentN identn.Config `mapstructure:"identn"`
}
// DeprecatedFlags are the flags that are deprecated and scheduled for removal.
@@ -176,6 +180,7 @@ func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.R
metricsexplorer.NewConfigFactory(),
flagger.NewConfigFactory(),
user.NewConfigFactory(),
identn.NewConfigFactory(),
}
conf, err := config.New(ctx, resolverConfig, configFactories)

View File

@@ -22,6 +22,9 @@ import (
"github.com/SigNoz/signoz/pkg/flagger/configflagger"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/global/signozglobal"
"github.com/SigNoz/signoz/pkg/identn"
"github.com/SigNoz/signoz/pkg/identn/apikeyidentn"
"github.com/SigNoz/signoz/pkg/identn/tokenizeridentn"
"github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
@@ -271,6 +274,13 @@ func NewTokenizerProviderFactories(cache cache.Cache, sqlstore sqlstore.SQLStore
)
}
func NewIdentNProviderFactories(sqlstore sqlstore.SQLStore, tokenizer tokenizer.Tokenizer) factory.NamedMap[factory.ProviderFactory[identn.IdentN, identn.Config]] {
return factory.MustNewNamedMap(
tokenizeridentn.NewFactory(tokenizer),
apikeyidentn.NewFactory(sqlstore),
)
}
func NewGlobalProviderFactories() factory.NamedMap[factory.ProviderFactory[global.Global, global.Config]] {
return factory.MustNewNamedMap(
signozglobal.NewFactory(),

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