mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-07 02:12:11 +00:00
Compare commits
1 Commits
ns/ext-api
...
update-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01d632cfe4 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -101,7 +101,7 @@
|
||||
|
||||
# Integration tests
|
||||
|
||||
/tests/integration/ @vikrantgupta25
|
||||
/tests/integration/ @vikrantgupta25, @srikanthccv
|
||||
|
||||
# OpenAPI types generator
|
||||
|
||||
|
||||
1
.github/workflows/build-enterprise.yaml
vendored
1
.github/workflows/build-enterprise.yaml
vendored
@@ -70,7 +70,6 @@ jobs:
|
||||
echo 'PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> frontend/.env
|
||||
echo 'APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> frontend/.env
|
||||
echo 'PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> frontend/.env
|
||||
echo 'DOCS_BASE_URL="https://signoz.io"' >> frontend/.env
|
||||
- name: cache-dotenv
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
||||
1
.github/workflows/build-staging.yaml
vendored
1
.github/workflows/build-staging.yaml
vendored
@@ -69,7 +69,6 @@ jobs:
|
||||
echo 'PYLON_APP_ID="${{ secrets.NP_PYLON_APP_ID }}"' >> frontend/.env
|
||||
echo 'APPCUES_APP_ID="${{ secrets.NP_APPCUES_APP_ID }}"' >> frontend/.env
|
||||
echo 'PYLON_IDENTITY_SECRET="${{ secrets.NP_PYLON_IDENTITY_SECRET }}"' >> frontend/.env
|
||||
echo 'DOCS_BASE_URL="https://staging.signoz.io"' >> frontend/.env
|
||||
- name: cache-dotenv
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
||||
11
.github/workflows/gor-signoz.yaml
vendored
11
.github/workflows/gor-signoz.yaml
vendored
@@ -3,8 +3,8 @@ name: gor-signoz
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+"
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -36,9 +36,8 @@ jobs:
|
||||
echo 'PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> .env
|
||||
echo 'APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> .env
|
||||
echo 'PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> .env
|
||||
echo 'DOCS_BASE_URL="https://signoz.io"' >> .env
|
||||
- name: build-frontend
|
||||
run: make js-build
|
||||
run: make js-build
|
||||
- name: upload-frontend-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -105,7 +104,7 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: "~> v2"
|
||||
version: '~> v2'
|
||||
args: release --config ${{ env.CONFIG_PATH }} --clean --split
|
||||
workdir: .
|
||||
env:
|
||||
@@ -162,7 +161,7 @@ jobs:
|
||||
if: steps.cache-linux.outputs.cache-hit == 'true' && steps.cache-darwin.outputs.cache-hit == 'true' # only run if caches hit
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: "~> v2"
|
||||
version: '~> v2'
|
||||
args: continue --merge
|
||||
workdir: .
|
||||
env:
|
||||
|
||||
@@ -12,7 +12,6 @@ linters:
|
||||
- misspell
|
||||
- nilnil
|
||||
- sloglint
|
||||
- wastedassign
|
||||
- unparam
|
||||
- unused
|
||||
settings:
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"eslint.workingDirectories": [
|
||||
"./frontend"
|
||||
],
|
||||
"eslint.workingDirectories": ["./frontend"],
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
@@ -81,15 +80,12 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, _ role.Setter, _ role.Granter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, _ role.Module, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
|
||||
},
|
||||
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return noopgateway.NewProviderFactory()
|
||||
},
|
||||
func(store sqlstore.SQLStore, authz authz.AuthZ, licensing licensing.Licensing, _ []role.RegisterTypeable) role.Setter {
|
||||
return implrole.NewSetter(implrole.NewStore(store), authz)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create signoz", "error", err)
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/ee/modules/role/implrole"
|
||||
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
|
||||
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
@@ -30,7 +29,6 @@ import (
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
pkgimplrole "github.com/SigNoz/signoz/pkg/modules/role/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
@@ -121,17 +119,13 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, roleSetter role.Setter, granter role.Granter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, roleSetter, granter, queryParser, querier, licensing)
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, role role.Module, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, role, queryParser, querier, licensing)
|
||||
},
|
||||
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return httpgateway.NewProviderFactory(licensing)
|
||||
},
|
||||
func(store sqlstore.SQLStore, authz authz.AuthZ, licensing licensing.Licensing, registry []role.RegisterTypeable) role.Setter {
|
||||
return implrole.NewSetter(pkgimplrole.NewStore(store), authz, licensing, registry)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create signoz", "error", err)
|
||||
return err
|
||||
|
||||
@@ -291,12 +291,3 @@ flagger:
|
||||
float:
|
||||
integer:
|
||||
object:
|
||||
|
||||
##################### User #####################
|
||||
user:
|
||||
password:
|
||||
reset:
|
||||
# Whether to allow users to reset their password themselves.
|
||||
allow_self: true
|
||||
# The duration within which a user can reset their password.
|
||||
max_token_lifetime: 6h
|
||||
|
||||
@@ -209,7 +209,7 @@ paths:
|
||||
/api/v1/dashboards/{id}/public:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: This endpoint deletes the public sharing config and disables the
|
||||
description: This endpoints deletes the public sharing config and disables the
|
||||
public sharing of a dashboard
|
||||
operationId: DeletePublicDashboard
|
||||
parameters:
|
||||
@@ -253,7 +253,7 @@ paths:
|
||||
- dashboard
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns public sharing config for a dashboard
|
||||
description: This endpoints returns public sharing config for a dashboard
|
||||
operationId: GetPublicDashboard
|
||||
parameters:
|
||||
- in: path
|
||||
@@ -301,7 +301,7 @@ paths:
|
||||
- dashboard
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint creates public sharing config and enables public
|
||||
description: This endpoints creates public sharing config and enables public
|
||||
sharing of the dashboard
|
||||
operationId: CreatePublicDashboard
|
||||
parameters:
|
||||
@@ -355,7 +355,7 @@ paths:
|
||||
- dashboard
|
||||
put:
|
||||
deprecated: false
|
||||
description: This endpoint updates the public sharing config for a dashboard
|
||||
description: This endpoints updates the public sharing config for a dashboard
|
||||
operationId: UpdatePublicDashboard
|
||||
parameters:
|
||||
- in: path
|
||||
@@ -671,7 +671,7 @@ paths:
|
||||
/api/v1/global/config:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns global config
|
||||
description: This endpoints returns global config
|
||||
operationId: GetGlobalConfig
|
||||
responses:
|
||||
"200":
|
||||
@@ -1447,7 +1447,8 @@ paths:
|
||||
/api/v1/public/dashboards/{id}:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns the sanitized dashboard data for public access
|
||||
description: This endpoints returns the sanitized dashboard data for public
|
||||
access
|
||||
operationId: GetPublicDashboardData
|
||||
parameters:
|
||||
- in: path
|
||||
@@ -1578,228 +1579,6 @@ paths:
|
||||
summary: Reset password
|
||||
tags:
|
||||
- users
|
||||
/api/v1/roles:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint lists all roles
|
||||
operationId: ListRoles
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/components/schemas/RoletypesRole'
|
||||
type: array
|
||||
status:
|
||||
type: string
|
||||
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 roles
|
||||
tags:
|
||||
- role
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint creates a role
|
||||
operationId: CreateRole
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TypesIdentifiable'
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
description: Created
|
||||
"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: Create role
|
||||
tags:
|
||||
- role
|
||||
/api/v1/roles/{id}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: This endpoint deletes a role
|
||||
operationId: DeleteRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: No Content
|
||||
"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: Delete role
|
||||
tags:
|
||||
- role
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint gets a role
|
||||
operationId: GetRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/RoletypesRole'
|
||||
status:
|
||||
type: string
|
||||
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: Get role
|
||||
tags:
|
||||
- role
|
||||
patch:
|
||||
deprecated: false
|
||||
description: This endpoint patches a role
|
||||
operationId: PatchRole
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: No Content
|
||||
"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: Patch role
|
||||
tags:
|
||||
- role
|
||||
/api/v1/user:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -2206,35 +1985,6 @@ paths:
|
||||
summary: Update user preference
|
||||
tags:
|
||||
- preferences
|
||||
/api/v2/factor_password/forgot:
|
||||
post:
|
||||
deprecated: false
|
||||
description: This endpoint initiates the forgot password flow by sending a reset
|
||||
password email
|
||||
operationId: ForgotPassword
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TypesPostableForgotPassword'
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
summary: Forgot password
|
||||
tags:
|
||||
- users
|
||||
/api/v2/features:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -4109,25 +3859,6 @@ components:
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
RoletypesRole:
|
||||
properties:
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
TypesChangePasswordRequest:
|
||||
properties:
|
||||
newPassword:
|
||||
@@ -4248,15 +3979,6 @@ components:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
TypesPostableForgotPassword:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
frontendBaseURL:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
type: object
|
||||
TypesPostableInvite:
|
||||
properties:
|
||||
email:
|
||||
@@ -4277,9 +3999,6 @@ components:
|
||||
type: object
|
||||
TypesResetPasswordToken:
|
||||
properties:
|
||||
expiresAt:
|
||||
format: date-time
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
passwordId:
|
||||
|
||||
@@ -47,7 +47,7 @@ func (provider *provider) Check(ctx context.Context, tuple *openfgav1.TupleKey)
|
||||
return provider.pkgAuthzService.Check(ctx, tuple)
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, _ authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -66,7 +66,7 @@ func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims aut
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, _ authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -26,13 +26,12 @@ type module struct {
|
||||
pkgDashboardModule dashboard.Module
|
||||
store dashboardtypes.Store
|
||||
settings factory.ScopedProviderSettings
|
||||
roleSetter role.Setter
|
||||
granter role.Granter
|
||||
role role.Module
|
||||
querier querier.Querier
|
||||
licensing licensing.Licensing
|
||||
}
|
||||
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, roleSetter role.Setter, granter role.Granter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, role role.Module, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard")
|
||||
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser)
|
||||
|
||||
@@ -40,8 +39,7 @@ func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, an
|
||||
pkgDashboardModule: pkgDashboardModule,
|
||||
store: store,
|
||||
settings: scopedProviderSettings,
|
||||
roleSetter: roleSetter,
|
||||
granter: granter,
|
||||
role: role,
|
||||
querier: querier,
|
||||
licensing: licensing,
|
||||
}
|
||||
@@ -61,12 +59,12 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
return errors.Newf(errors.TypeAlreadyExists, dashboardtypes.ErrCodePublicDashboardAlreadyExists, "dashboard with id %s is already public", storablePublicDashboard.DashboardID)
|
||||
}
|
||||
|
||||
role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
|
||||
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.granter.Grant(ctx, orgID, roletypes.SigNozAnonymousRoleName, authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.StringValue(), orgID, nil))
|
||||
err = module.role.Assign(ctx, role.ID, orgID, authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.StringValue(), orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -79,7 +77,7 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, []*authtypes.Object{additionObject}, nil)
|
||||
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, []*authtypes.Object{additionObject}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -195,7 +193,7 @@ func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashb
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
|
||||
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -208,7 +206,7 @@ func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashb
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
|
||||
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -272,7 +270,7 @@ func (module *module) deletePublic(ctx context.Context, orgID valuer.UUID, dashb
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
|
||||
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -285,7 +283,7 @@ func (module *module) deletePublic(ctx context.Context, orgID valuer.UUID, dashb
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
|
||||
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
package implrole
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type setter struct {
|
||||
store roletypes.Store
|
||||
authz authz.AuthZ
|
||||
licensing licensing.Licensing
|
||||
registry []role.RegisterTypeable
|
||||
}
|
||||
|
||||
func NewSetter(store roletypes.Store, authz authz.AuthZ, licensing licensing.Licensing, registry []role.RegisterTypeable) role.Setter {
|
||||
return &setter{
|
||||
store: store,
|
||||
authz: authz,
|
||||
licensing: licensing,
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
func (setter *setter) Create(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return setter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (setter *setter) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) (*roletypes.Role, error) {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
existingRole, err := setter.store.GetByOrgIDAndName(ctx, role.OrgID, role.Name)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if existingRole != nil {
|
||||
return roletypes.NewRoleFromStorableRole(existingRole), nil
|
||||
}
|
||||
|
||||
err = setter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (setter *setter) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
typeables := make([]authtypes.Typeable, 0)
|
||||
for _, register := range setter.registry {
|
||||
typeables = append(typeables, register.MustGetTypeables()...)
|
||||
}
|
||||
// role module cannot self register itself!
|
||||
typeables = append(typeables, setter.MustGetTypeables()...)
|
||||
|
||||
resources := make([]*authtypes.Resource, 0)
|
||||
for _, typeable := range typeables {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (setter *setter) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
storableRole, err := setter.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects := make([]*authtypes.Object, 0)
|
||||
for _, resource := range setter.GetResources(ctx) {
|
||||
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
|
||||
resourceObjects, err := setter.
|
||||
authz.
|
||||
ListObjects(
|
||||
ctx,
|
||||
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
|
||||
relation,
|
||||
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects = append(objects, resourceObjects...)
|
||||
}
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func (setter *setter) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return setter.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (setter *setter) PatchObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
additionTuples, err := roletypes.GetAdditionTuples(id, orgID, relation, additions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionTuples, err := roletypes.GetDeletionTuples(id, orgID, relation, deletions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setter.authz.Write(ctx, additionTuples, deletionTuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (setter *setter) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableRole, err := setter.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
err = role.CanEditDelete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setter.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (setter *setter) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
|
||||
}
|
||||
@@ -211,7 +211,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||
|
||||
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
|
||||
r := baseapp.NewRouter()
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz, s.signoz.Modules.RoleGetter)
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz)
|
||||
|
||||
r.Use(otelmux.Middleware(
|
||||
"apiserver",
|
||||
|
||||
@@ -38,7 +38,7 @@ module.exports = {
|
||||
'import', // Import/export linting
|
||||
'sonarjs', // Code quality/complexity
|
||||
// TODO: Uncomment after running: yarn add -D eslint-plugin-spellcheck
|
||||
// 'spellcheck', // Correct spellings
|
||||
// 'spellcheck',
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
@@ -60,18 +60,12 @@ module.exports = {
|
||||
'no-debugger': 'error', // Disallows debugger statements in production code
|
||||
curly: 'error', // Requires curly braces for all control statements
|
||||
eqeqeq: ['error', 'always', { null: 'ignore' }], // Enforces === and !== (allows == null for null/undefined check)
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }], // Warns on console.log, allows console.warn/error
|
||||
// TODO: Enable after fixing ~15 console.log statements
|
||||
// 'no-console': ['error', { allow: ['warn', 'error'] }], // Warns on console.log, allows console.warn/error
|
||||
|
||||
// TypeScript rules
|
||||
'@typescript-eslint/explicit-function-return-type': 'error', // Requires explicit return types on functions
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
// Disallows unused variables/args
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_', // Allows unused args prefixed with _ (e.g., _unusedParam)
|
||||
varsIgnorePattern: '^_', // Allows unused vars prefixed with _ (e.g., _unusedVar)
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'warn', // Warns when using 'any' type (consider upgrading to error)
|
||||
// TODO: Change to 'error' after fixing ~80 empty function placeholders in providers/contexts
|
||||
'@typescript-eslint/no-empty-function': 'off', // Disallows empty function bodies
|
||||
|
||||
@@ -41,7 +41,7 @@ export const getConsumerLagDetails = async (
|
||||
> => {
|
||||
const { detailType, ...restProps } = props;
|
||||
const response = await axios.post(
|
||||
`/messaging-queues/kafka/consumer-lag/${detailType}`,
|
||||
`/messaging-queues/kafka/consumer-lag/${props.detailType}`,
|
||||
{
|
||||
...restProps,
|
||||
},
|
||||
|
||||
@@ -43,17 +43,16 @@ export const omitIdFromQuery = (query: Query | null): any => ({
|
||||
builder: {
|
||||
...query?.builder,
|
||||
queryData: query?.builder.queryData.map((queryData) => {
|
||||
const { id: _aggregateAttributeId, ...rest } =
|
||||
queryData.aggregateAttribute || {};
|
||||
const { id, ...rest } = queryData.aggregateAttribute || {};
|
||||
const newAggregateAttribute = rest;
|
||||
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
|
||||
const { id: _groupByAttributeId, ...rest } = groupByAttribute;
|
||||
const { id, ...rest } = groupByAttribute;
|
||||
return rest;
|
||||
});
|
||||
const newItems = queryData.filters?.items?.map((item) => {
|
||||
const { id: _itemId, ...newItem } = item;
|
||||
const { id, ...newItem } = item;
|
||||
if (item.key) {
|
||||
const { id: _keyId, ...rest } = item.key;
|
||||
const { id, ...rest } = item.key;
|
||||
return {
|
||||
...newItem,
|
||||
key: rest,
|
||||
|
||||
@@ -45,6 +45,7 @@ function Pre({
|
||||
}
|
||||
|
||||
function Code({
|
||||
node,
|
||||
inline,
|
||||
className = 'blog-code',
|
||||
children,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
QUERY_BUILDER_OPERATORS_BY_KEY_TYPE,
|
||||
queryOperatorSuggestions,
|
||||
} from 'constants/antlrQueryConstants';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { debounce, isNull } from 'lodash-es';
|
||||
@@ -207,6 +208,8 @@ function QuerySearch({
|
||||
const lastValueRef = useRef<string>('');
|
||||
const isMountedRef = useRef<boolean>(true);
|
||||
|
||||
const { handleRunQuery } = useQueryBuilder();
|
||||
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const dynamicVariables = useMemo(
|
||||
|
||||
@@ -87,7 +87,7 @@ function TraceOperatorEditor({
|
||||
// Track if the query was changed externally (from props) vs internally (user input)
|
||||
const [isExternalQueryChange, setIsExternalQueryChange] = useState(false);
|
||||
const [lastExternalValue, setLastExternalValue] = useState<string>('');
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { currentQuery, handleRunQuery } = useQueryBuilder();
|
||||
|
||||
const queryOptions = useMemo(
|
||||
() =>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { EditorView } from '@uiw/react-codemirror';
|
||||
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
|
||||
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import * as UseQBModule from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { fireEvent, render, userEvent, waitFor } from 'tests/test-utils';
|
||||
import type { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -120,8 +121,13 @@ jest.mock('api/querySuggestions/getValueSuggestion', () => ({
|
||||
// Note: We're NOT mocking CodeMirror here - using the real component
|
||||
// This provides integration testing with the actual CodeMirror editor
|
||||
|
||||
const handleRunQueryMock = ((UseQBModule as unknown) as {
|
||||
handleRunQuery: jest.MockedFunction<() => void>;
|
||||
}).handleRunQuery;
|
||||
|
||||
const SAMPLE_KEY_TYPING = 'http.';
|
||||
const SAMPLE_VALUE_TYPING_INCOMPLETE = "service.name = '";
|
||||
const SAMPLE_VALUE_TYPING_COMPLETE = "service.name = 'frontend'";
|
||||
const SAMPLE_STATUS_QUERY = "http.status_code = '200'";
|
||||
|
||||
describe('QuerySearch (Integration with Real CodeMirror)', () => {
|
||||
|
||||
@@ -796,12 +796,12 @@ export const adjustQueryForV5 = (currentQuery: Query): Query => {
|
||||
});
|
||||
|
||||
const {
|
||||
aggregateAttribute: _aggregateAttribute,
|
||||
aggregateOperator: _aggregateOperator,
|
||||
timeAggregation: _timeAggregation,
|
||||
spaceAggregation: _spaceAggregation,
|
||||
reduceTo: _reduceTo,
|
||||
filters: _filters,
|
||||
aggregateAttribute,
|
||||
aggregateOperator,
|
||||
timeAggregation,
|
||||
spaceAggregation,
|
||||
reduceTo,
|
||||
filters,
|
||||
...retainedQuery
|
||||
} = query;
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import './Slider.styles.scss';
|
||||
|
||||
export default function Slider(): JSX.Element {
|
||||
import { IQuickFiltersConfig } from 'components/QuickFilters/types';
|
||||
|
||||
interface ISliderProps {
|
||||
filter: IQuickFiltersConfig;
|
||||
}
|
||||
|
||||
// not needed for now build when required
|
||||
export default function Slider(props: ISliderProps): JSX.Element {
|
||||
const { filter } = props;
|
||||
console.log(filter);
|
||||
return <div>Slider</div>;
|
||||
}
|
||||
|
||||
@@ -303,9 +303,15 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
/>
|
||||
);
|
||||
case FiltersType.DURATION:
|
||||
return <Duration filter={filter} onFilterChange={onFilterChange} />;
|
||||
return (
|
||||
<Duration
|
||||
filter={filter}
|
||||
onFilterChange={onFilterChange}
|
||||
source={source}
|
||||
/>
|
||||
);
|
||||
case FiltersType.SLIDER:
|
||||
return <Slider />;
|
||||
return <Slider filter={filter} />;
|
||||
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
||||
default:
|
||||
return (
|
||||
|
||||
@@ -26,7 +26,6 @@ import { ApiMonitoringHardcodedAttributeKeys } from '../../constants';
|
||||
import { DEFAULT_PARAMS, useApiMonitoringParams } from '../../queryParams';
|
||||
import { columnsConfig, formatDataForTable } from '../../utils';
|
||||
import DomainDetails from './DomainDetails/DomainDetails';
|
||||
import DOCLINKS from 'utils/docLinks';
|
||||
|
||||
function DomainList(): JSX.Element {
|
||||
const [params, setParams] = useApiMonitoringParams();
|
||||
@@ -146,17 +145,7 @@ function DomainList(): JSX.Element {
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-domains-message">
|
||||
No External API calls detected. To automatically detect them, ensure
|
||||
Client spans are being sent with required attributes.
|
||||
<br />
|
||||
Read more about <span> </span>
|
||||
<a
|
||||
href={DOCLINKS.EXTERNAL_API_MONITORING}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
configuring External API monitoring.
|
||||
</a>
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { defaultPostableAlertRuleV2 } from 'container/CreateAlertV2/constants';
|
||||
import { getCreateAlertLocalStateFromAlertDef } from 'container/CreateAlertV2/utils';
|
||||
|
||||
@@ -73,7 +73,7 @@ export function sanitizeDashboardData(
|
||||
|
||||
const updatedVariables = Object.entries(selectedData.variables).reduce(
|
||||
(acc, [key, value]) => {
|
||||
const { selectedValue: _selectedValue, ...rest } = value;
|
||||
const { selectedValue, ...rest } = value;
|
||||
acc[key] = rest;
|
||||
return acc;
|
||||
},
|
||||
|
||||
@@ -9,11 +9,10 @@ import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { Atom, Terminal } from 'lucide-react';
|
||||
import { Atom, Play, Terminal } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
@@ -166,8 +165,9 @@ function QuerySection({
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<RunQueryBtn
|
||||
onStageRunQuery={(): void => {
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={(): void => {
|
||||
runQuery();
|
||||
logEvent('Alert: Stage and run query', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertType],
|
||||
@@ -176,7 +176,11 @@ function QuerySection({
|
||||
queryType: queryCategory,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
className="stage-run-query"
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
Stage & Run Query
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
items={tabs}
|
||||
@@ -195,7 +199,14 @@ function QuerySection({
|
||||
onChange={handleQueryCategoryChange}
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<RunQueryBtn onStageRunQuery={runQuery} />
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={runQuery}
|
||||
className="stage-run-query"
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
Stage & Run Query
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
items={items}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Input, Typography } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Flex,
|
||||
Input,
|
||||
MenuProps,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
|
||||
@@ -91,7 +91,7 @@ function Summary(): JSX.Element {
|
||||
const queryFiltersWithoutId = useMemo(() => {
|
||||
const filtersWithoutId = {
|
||||
...queryFilters,
|
||||
items: queryFilters.items.map(({ id: _id, ...rest }) => rest),
|
||||
items: queryFilters.items.map(({ id, ...rest }) => rest),
|
||||
};
|
||||
return JSON.stringify(filtersWithoutId);
|
||||
}, [queryFilters]);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
getDefaultWidgetData,
|
||||
PANEL_TYPE_TO_QUERY_TYPES,
|
||||
} from 'container/NewWidget/utils';
|
||||
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
|
||||
// import { QueryBuilder } from 'container/QueryBuilder';
|
||||
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
@@ -21,7 +20,7 @@ import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { defaultTo, isUndefined } from 'lodash-es';
|
||||
import { Atom, Terminal } from 'lucide-react';
|
||||
import { Atom, Play, Terminal } from 'lucide-react';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
getNextWidgets,
|
||||
@@ -29,14 +28,20 @@ import {
|
||||
getSelectedWidgetIndex,
|
||||
} from 'providers/Dashboard/util';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
|
||||
import PromQLQueryContainer from './QueryBuilder/promQL';
|
||||
|
||||
function QuerySection({ selectedGraph }: QueryProps): JSX.Element {
|
||||
function QuerySection({
|
||||
selectedGraph,
|
||||
queryResponse,
|
||||
}: QueryProps): JSX.Element {
|
||||
const {
|
||||
currentQuery,
|
||||
handleRunQuery: handleRunQueryFromQueryBuilder,
|
||||
@@ -237,7 +242,15 @@ function QuerySection({ selectedGraph }: QueryProps): JSX.Element {
|
||||
tabBarExtraContent={
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<TextToolTip text="This will temporarily save the current query and graph state. This will persist across tab change" />
|
||||
<RunQueryBtn label="Stage & Run Query" onStageRunQuery={handleRunQuery} />
|
||||
<Button
|
||||
loading={queryResponse.isFetching}
|
||||
type="primary"
|
||||
onClick={handleRunQuery}
|
||||
className="stage-run-query"
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
Stage & Run Query
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
items={items}
|
||||
@@ -248,6 +261,10 @@ function QuerySection({ selectedGraph }: QueryProps): JSX.Element {
|
||||
|
||||
interface QueryProps {
|
||||
selectedGraph: PANEL_TYPES;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
}
|
||||
|
||||
export default QuerySection;
|
||||
|
||||
@@ -64,7 +64,7 @@ function LeftContainer({
|
||||
enableDrillDown={enableDrillDown}
|
||||
/>
|
||||
<QueryContainer className="query-section-left-container">
|
||||
<QuerySection selectedGraph={selectedGraph} />
|
||||
<QuerySection selectedGraph={selectedGraph} queryResponse={queryResponse} />
|
||||
{selectedGraph === PANEL_TYPES.LIST && (
|
||||
<ExplorerColumnsRenderer
|
||||
selectedLogFields={selectedLogFields}
|
||||
|
||||
@@ -166,7 +166,7 @@ function UpdateContextLinks({
|
||||
onSave(newContextLink);
|
||||
} catch (error) {
|
||||
// Form validation failed, don't call onSave
|
||||
console.error('Form validation failed:', error);
|
||||
console.log('Form validation failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
.run-query-btn {
|
||||
display: flex;
|
||||
min-width: 132px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.ant-btn-icon {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-query-btn {
|
||||
display: flex;
|
||||
min-width: 132px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.cmd-hint {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
//not using var here to support opacity 60%. To be handled at design system level.
|
||||
background: rgba(35, 38, 46, 0.6);
|
||||
line-height: 1;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.cmd-hint {
|
||||
color: var(--bg-ink-200);
|
||||
//not using var here to support opacity 60%. To be handled at design system level.
|
||||
background: rgba(231, 232, 236, 0.8);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import './RunQueryBtn.scss';
|
||||
|
||||
import { Button } from 'antd';
|
||||
import {
|
||||
ChevronUp,
|
||||
Command,
|
||||
CornerDownLeft,
|
||||
Loader2,
|
||||
Play,
|
||||
} from 'lucide-react';
|
||||
import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
|
||||
|
||||
interface RunQueryBtnProps {
|
||||
label?: string;
|
||||
isLoadingQueries?: boolean;
|
||||
handleCancelQuery?: () => void;
|
||||
onStageRunQuery?: () => void;
|
||||
}
|
||||
|
||||
function RunQueryBtn({
|
||||
label,
|
||||
isLoadingQueries,
|
||||
handleCancelQuery,
|
||||
onStageRunQuery,
|
||||
}: RunQueryBtnProps): JSX.Element {
|
||||
const isMac = getUserOperatingSystem() === UserOperatingSystem.MACOS;
|
||||
return isLoadingQueries ? (
|
||||
<Button
|
||||
type="default"
|
||||
icon={<Loader2 size={14} className="loading-icon animate-spin" />}
|
||||
className="cancel-query-btn periscope-btn danger"
|
||||
onClick={handleCancelQuery}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
className="run-query-btn periscope-btn primary"
|
||||
disabled={isLoadingQueries || !onStageRunQuery}
|
||||
onClick={onStageRunQuery}
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
{label || 'Run Query'}
|
||||
<div className="cmd-hint">
|
||||
{isMac ? <Command size={12} /> : <ChevronUp size={12} />}
|
||||
<CornerDownLeft size={12} />
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default RunQueryBtn;
|
||||
@@ -1,82 +0,0 @@
|
||||
// frontend/src/container/QueryBuilder/components/RunQueryBtn/__tests__/RunQueryBtn.test.tsx
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
|
||||
import RunQueryBtn from '../RunQueryBtn';
|
||||
|
||||
// Mock OS util
|
||||
jest.mock('utils/getUserOS', () => ({
|
||||
getUserOperatingSystem: jest.fn(),
|
||||
UserOperatingSystem: { MACOS: 'mac', WINDOWS: 'win', LINUX: 'linux' },
|
||||
}));
|
||||
import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
|
||||
|
||||
describe('RunQueryBtn', () => {
|
||||
test('renders run state and triggers on click', () => {
|
||||
(getUserOperatingSystem as jest.Mock).mockReturnValue(
|
||||
UserOperatingSystem.MACOS,
|
||||
);
|
||||
const onRun = jest.fn();
|
||||
render(<RunQueryBtn onStageRunQuery={onRun} />);
|
||||
const btn = screen.getByRole('button', { name: /run query/i });
|
||||
expect(btn).toBeEnabled();
|
||||
fireEvent.click(btn);
|
||||
expect(onRun).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('disabled when onStageRunQuery is undefined', () => {
|
||||
(getUserOperatingSystem as jest.Mock).mockReturnValue(
|
||||
UserOperatingSystem.MACOS,
|
||||
);
|
||||
render(<RunQueryBtn />);
|
||||
expect(screen.getByRole('button', { name: /run query/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
test('shows cancel state and calls handleCancelQuery', () => {
|
||||
(getUserOperatingSystem as jest.Mock).mockReturnValue(
|
||||
UserOperatingSystem.MACOS,
|
||||
);
|
||||
const onCancel = jest.fn();
|
||||
render(<RunQueryBtn isLoadingQueries handleCancelQuery={onCancel} />);
|
||||
const cancel = screen.getByRole('button', { name: /cancel/i });
|
||||
fireEvent.click(cancel);
|
||||
expect(onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('shows Command + CornerDownLeft on mac', () => {
|
||||
(getUserOperatingSystem as jest.Mock).mockReturnValue(
|
||||
UserOperatingSystem.MACOS,
|
||||
);
|
||||
const { container } = render(
|
||||
<RunQueryBtn onStageRunQuery={(): void => {}} />,
|
||||
);
|
||||
expect(container.querySelector('.lucide-command')).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector('.lucide-corner-down-left'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows ChevronUp + CornerDownLeft on non-mac', () => {
|
||||
(getUserOperatingSystem as jest.Mock).mockReturnValue(
|
||||
UserOperatingSystem.WINDOWS,
|
||||
);
|
||||
const { container } = render(
|
||||
<RunQueryBtn onStageRunQuery={(): void => {}} />,
|
||||
);
|
||||
expect(container.querySelector('.lucide-chevron-up')).toBeInTheDocument();
|
||||
expect(container.querySelector('.lucide-command')).not.toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector('.lucide-corner-down-left'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders custom label when provided', () => {
|
||||
(getUserOperatingSystem as jest.Mock).mockReturnValue(
|
||||
UserOperatingSystem.MACOS,
|
||||
);
|
||||
const onRun = jest.fn();
|
||||
render(<RunQueryBtn onStageRunQuery={onRun} label="Stage & Run Query" />);
|
||||
expect(
|
||||
screen.getByRole('button', { name: /stage & run query/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
import './ToolbarActions.styles.scss';
|
||||
|
||||
import { Button } from 'antd';
|
||||
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { Loader2, Play } from 'lucide-react';
|
||||
import { MutableRefObject, useEffect } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
|
||||
import RunQueryBtn from '../RunQueryBtn/RunQueryBtn';
|
||||
|
||||
interface RightToolbarActionsProps {
|
||||
onStageRunQuery: () => void;
|
||||
isLoadingQueries?: boolean;
|
||||
@@ -42,7 +42,14 @@ export default function RightToolbarActions({
|
||||
if (showLiveLogs) {
|
||||
return (
|
||||
<div className="right-toolbar-actions-container">
|
||||
<RunQueryBtn />
|
||||
<Button
|
||||
type="primary"
|
||||
className="run-query-btn periscope-btn primary"
|
||||
disabled
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
Run Query
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -58,11 +65,26 @@ export default function RightToolbarActions({
|
||||
|
||||
return (
|
||||
<div className="right-toolbar-actions-container">
|
||||
<RunQueryBtn
|
||||
isLoadingQueries={isLoadingQueries}
|
||||
handleCancelQuery={handleCancelQuery}
|
||||
onStageRunQuery={onStageRunQuery}
|
||||
/>
|
||||
{isLoadingQueries ? (
|
||||
<Button
|
||||
type="default"
|
||||
icon={<Loader2 size={14} className="loading-icon animate-spin" />}
|
||||
className="cancel-query-btn periscope-btn danger"
|
||||
onClick={handleCancelQuery}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
className="run-query-btn periscope-btn primary"
|
||||
disabled={isLoadingQueries}
|
||||
onClick={onStageRunQuery}
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
Run Query
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ const useAggregateDrilldown = ({
|
||||
query,
|
||||
// panelType,
|
||||
aggregateData: aggregateDataWithTimeRange,
|
||||
widgetId,
|
||||
onClose,
|
||||
});
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ const useBaseAggregateOptions = ({
|
||||
|
||||
const handleBaseDrilldown = useCallback(
|
||||
(key: string): void => {
|
||||
console.log('Base drilldown:', { key, aggregateData });
|
||||
const route = getRoute(key);
|
||||
const timeRange = aggregateData?.timeRange;
|
||||
const filtersToAdd = aggregateData?.filters || [];
|
||||
|
||||
@@ -17,6 +17,7 @@ interface UseBaseAggregateOptionsProps {
|
||||
query?: Query;
|
||||
// panelType?: PANEL_TYPES;
|
||||
aggregateData?: AggregateData | null;
|
||||
widgetId?: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -26,6 +27,7 @@ const useDashboardVarConfig = ({
|
||||
query,
|
||||
// panelType,
|
||||
aggregateData,
|
||||
widgetId,
|
||||
onClose,
|
||||
}: UseBaseAggregateOptionsProps): {
|
||||
dashbaordVariablesConfig: {
|
||||
@@ -81,6 +83,11 @@ const useDashboardVarConfig = ({
|
||||
dashboardVar: [string, IDashboardVariable],
|
||||
fieldValue: any,
|
||||
) => {
|
||||
console.log('Setting variable:', {
|
||||
fieldName,
|
||||
dashboardVarId: dashboardVar[0],
|
||||
fieldValue,
|
||||
});
|
||||
onValueUpdate(fieldName, dashboardVar[1]?.id, fieldValue, false);
|
||||
onClose();
|
||||
},
|
||||
@@ -89,6 +96,10 @@ const useDashboardVarConfig = ({
|
||||
|
||||
const handleUnsetVariable = useCallback(
|
||||
(fieldName: string, dashboardVar: [string, IDashboardVariable]) => {
|
||||
console.log('Unsetting variable:', {
|
||||
fieldName,
|
||||
dashboardVarId: dashboardVar[0],
|
||||
});
|
||||
onValueUpdate(fieldName, dashboardVar[0], null, false);
|
||||
onClose();
|
||||
},
|
||||
@@ -98,6 +109,12 @@ const useDashboardVarConfig = ({
|
||||
const handleCreateVariable = useCallback(
|
||||
(fieldName: string, fieldValue: string | number | boolean) => {
|
||||
const source = getSourceFromQuery();
|
||||
console.log('Creating variable from drilldown:', {
|
||||
fieldName,
|
||||
fieldValue,
|
||||
source,
|
||||
widgetId,
|
||||
});
|
||||
createVariable(
|
||||
fieldName,
|
||||
fieldValue,
|
||||
@@ -108,7 +125,7 @@ const useDashboardVarConfig = ({
|
||||
);
|
||||
onClose();
|
||||
},
|
||||
[createVariable, getSourceFromQuery, onClose],
|
||||
[createVariable, getSourceFromQuery, widgetId, onClose],
|
||||
);
|
||||
|
||||
const contextItems = useMemo(
|
||||
|
||||
@@ -19,6 +19,20 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.cancel-query-btn {
|
||||
min-width: 96px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.run-query-btn {
|
||||
min-width: 96px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ export const normalizeSteps = (steps: FunnelStepData[]): FunnelStepData[] => {
|
||||
...step.filters,
|
||||
items: step.filters.items.map((item) => {
|
||||
const {
|
||||
id: _unusedId,
|
||||
isIndexed: _isIndexed,
|
||||
id: unusedId,
|
||||
isIndexed,
|
||||
...keyObj
|
||||
} = item.key as BaseAutocompleteData;
|
||||
return {
|
||||
|
||||
@@ -143,7 +143,7 @@ export const useValidateFunnelSteps = ({
|
||||
selectedTime,
|
||||
steps.map((step) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { latency_type: _latency_type, ...rest } = step;
|
||||
const { latency_type, ...rest } = step;
|
||||
return rest;
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -55,7 +55,7 @@ const useDragColumns = <T>(storageKey: LOCALSTORAGE): UseDragColumns<T> => {
|
||||
const parsedDraggedColumns = await JSON.parse(localStorageColumns);
|
||||
nextDraggedColumns = parsedDraggedColumns;
|
||||
} catch (e) {
|
||||
console.error('error while parsing json: ', e);
|
||||
console.log('error while parsing json');
|
||||
} finally {
|
||||
redirectWithDraggedColumns(nextDraggedColumns);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ import parser from 'lib/logql/parser';
|
||||
describe('lib/logql/parser', () => {
|
||||
test('parse valid queries', () => {
|
||||
logqlQueries.forEach((queryObject) => {
|
||||
try {
|
||||
parser(queryObject.query);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
expect(parser(queryObject.query)).toEqual(queryObject.parsedQuery);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,11 @@ import { reverseParser } from 'lib/logql/reverseParser';
|
||||
describe('lib/logql/reverseParser', () => {
|
||||
test('reverse parse valid queries', () => {
|
||||
logqlQueries.forEach((queryObject) => {
|
||||
expect(reverseParser(queryObject.parsedQuery)).toEqual(queryObject.query);
|
||||
try {
|
||||
expect(reverseParser(queryObject.parsedQuery)).toEqual(queryObject.query);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,8 +11,8 @@ describe('getYAxisScale', () => {
|
||||
keyIndex: 1,
|
||||
thresholdValue: 10,
|
||||
thresholdUnit: 'percentunit',
|
||||
moveThreshold(): void {
|
||||
// no-op
|
||||
moveThreshold(dragIndex, hoverIndex): void {
|
||||
console.log(dragIndex, hoverIndex);
|
||||
},
|
||||
selectedGraph: PANEL_TYPES.TIME_SERIES,
|
||||
},
|
||||
@@ -21,8 +21,8 @@ describe('getYAxisScale', () => {
|
||||
keyIndex: 2,
|
||||
thresholdValue: 20,
|
||||
thresholdUnit: 'percentunit',
|
||||
moveThreshold(): void {
|
||||
// no-op
|
||||
moveThreshold(dragIndex, hoverIndex): void {
|
||||
console.log(dragIndex, hoverIndex);
|
||||
},
|
||||
selectedGraph: PANEL_TYPES.TIME_SERIES,
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MessagingQueuesPayloadProps,
|
||||
} from 'api/messagingQueues/getConsumerLagDetails';
|
||||
import axios from 'axios';
|
||||
import { isNumber } from 'chart.js/helpers';
|
||||
import cx from 'classnames';
|
||||
import { ColumnTypeRender } from 'components/Logs/TableView/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
|
||||
@@ -275,7 +275,7 @@ export function setConfigDetail(
|
||||
},
|
||||
): void {
|
||||
// remove "key" and its value from the paramsToSet object
|
||||
const { key: _key, ...restParamsToSet } = paramsToSet || {};
|
||||
const { key, ...restParamsToSet } = paramsToSet || {};
|
||||
|
||||
if (!isEmpty(restParamsToSet)) {
|
||||
const configDetail = {
|
||||
|
||||
@@ -145,7 +145,7 @@ export const removeFilter = (
|
||||
const updatedValues = prevValue.filter((item: any) => item !== value);
|
||||
|
||||
if (updatedValues.length === 0) {
|
||||
const { [filterType]: _item, ...remainingFilters } = prevFilters;
|
||||
const { [filterType]: item, ...remainingFilters } = prevFilters;
|
||||
return Object.keys(remainingFilters).length > 0
|
||||
? (remainingFilters as FilterType)
|
||||
: undefined;
|
||||
@@ -175,7 +175,7 @@ export const removeAllFilters = (
|
||||
return prevFilters;
|
||||
}
|
||||
|
||||
const { [filterType]: _item, ...remainingFilters } = prevFilters;
|
||||
const { [filterType]: item, ...remainingFilters } = prevFilters;
|
||||
|
||||
return Object.keys(remainingFilters).length > 0
|
||||
? (remainingFilters as Record<
|
||||
|
||||
@@ -20,7 +20,8 @@ export const parseQueryIntoSpanKind = (
|
||||
current = parsedValue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error while parsing json: ', error);
|
||||
console.log(error);
|
||||
console.log('error while parsing json');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export const parseQueryIntoCurrent = (
|
||||
current = parseInt(parsedValue, 10);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error while parsing json: ', error);
|
||||
console.log('error while parsing json');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ export const parseQueryIntoOrder = (
|
||||
current = parsedValue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error while parsing json: ', error);
|
||||
console.log(error);
|
||||
console.log('error while parsing json');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ export const parseAggregateOrderParams = (
|
||||
current = parsedValue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error while parsing json: ', error);
|
||||
console.log(error);
|
||||
console.log('error while parsing json');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ const DOCLINKS = {
|
||||
'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=traces-explorer-trace-tab#traces-view',
|
||||
METRICS_EXPLORER_EMPTY_STATE:
|
||||
'https://signoz.io/docs/userguide/send-metrics-cloud/',
|
||||
EXTERNAL_API_MONITORING:
|
||||
'https://signoz.io/docs/external-api-monitoring/overview/',
|
||||
};
|
||||
|
||||
export default DOCLINKS;
|
||||
|
||||
@@ -17,7 +17,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
ID: "CreatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Create public dashboard",
|
||||
Description: "This endpoint creates public sharing config and enables public sharing of the dashboard",
|
||||
Description: "This endpoints creates public sharing config and enables public sharing of the dashboard",
|
||||
Request: new(dashboardtypes.PostablePublicDashboard),
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
@@ -34,7 +34,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
ID: "GetPublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Get public dashboard",
|
||||
Description: "This endpoint returns public sharing config for a dashboard",
|
||||
Description: "This endpoints returns public sharing config for a dashboard",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(dashboardtypes.GettablePublicDasbhboard),
|
||||
@@ -51,7 +51,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
ID: "UpdatePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Update public dashboard",
|
||||
Description: "This endpoint updates the public sharing config for a dashboard",
|
||||
Description: "This endpoints updates the public sharing config for a dashboard",
|
||||
Request: new(dashboardtypes.UpdatablePublicDashboard),
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
@@ -68,7 +68,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
ID: "DeletePublicDashboard",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Delete public dashboard",
|
||||
Description: "This endpoint deletes the public sharing config and disables the public sharing of a dashboard",
|
||||
Description: "This endpoints deletes the public sharing config and disables the public sharing of a dashboard",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
@@ -83,7 +83,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}", handler.New(provider.authZ.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicData,
|
||||
authtypes.RelationRead,
|
||||
authtypes.RelationRead, authtypes.RelationRead,
|
||||
dashboardtypes.TypeableMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
@@ -92,11 +92,11 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs)
|
||||
}, []string{}), handler.OpenAPIDef{
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "GetPublicDashboardData",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Get public dashboard data",
|
||||
Description: "This endpoint returns the sanitized dashboard data for public access",
|
||||
Description: "This endpoints returns the sanitized dashboard data for public access",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(dashboardtypes.GettablePublicDashboardData),
|
||||
@@ -111,7 +111,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
|
||||
if err := router.Handle("/api/v1/public/dashboards/{id}/widgets/{idx}/query_range", handler.New(provider.authZ.CheckWithoutClaims(
|
||||
provider.dashboardHandler.GetPublicWidgetQueryRange,
|
||||
authtypes.RelationRead,
|
||||
authtypes.RelationRead, authtypes.RelationRead,
|
||||
dashboardtypes.TypeableMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
@@ -120,7 +120,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
|
||||
}
|
||||
|
||||
return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs)
|
||||
}, []string{}), handler.OpenAPIDef{
|
||||
}), handler.OpenAPIDef{
|
||||
ID: "GetPublicDashboardWidgetQueryRange",
|
||||
Tags: []string{"dashboard"},
|
||||
Summary: "Get query range result",
|
||||
|
||||
@@ -13,7 +13,7 @@ func (provider *provider) addGlobalRoutes(router *mux.Router) error {
|
||||
ID: "GetGlobalConfig",
|
||||
Tags: []string{"global"},
|
||||
Summary: "Get global config",
|
||||
Description: "This endpoint returns global config",
|
||||
Description: "This endpoints returns global config",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.GettableGlobalConfig),
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/promote"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
@@ -42,8 +41,6 @@ type provider struct {
|
||||
dashboardHandler dashboard.Handler
|
||||
metricsExplorerHandler metricsexplorer.Handler
|
||||
gatewayHandler gateway.Handler
|
||||
roleGetter role.Getter
|
||||
roleHandler role.Handler
|
||||
}
|
||||
|
||||
func NewFactory(
|
||||
@@ -61,11 +58,9 @@ func NewFactory(
|
||||
dashboardHandler dashboard.Handler,
|
||||
metricsExplorerHandler metricsexplorer.Handler,
|
||||
gatewayHandler gateway.Handler,
|
||||
roleGetter role.Getter,
|
||||
roleHandler role.Handler,
|
||||
) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) {
|
||||
return newProvider(ctx, providerSettings, config, orgGetter, authz, orgHandler, userHandler, sessionHandler, authDomainHandler, preferenceHandler, globalHandler, promoteHandler, flaggerHandler, dashboardModule, dashboardHandler, metricsExplorerHandler, gatewayHandler, roleGetter, roleHandler)
|
||||
return newProvider(ctx, providerSettings, config, orgGetter, authz, orgHandler, userHandler, sessionHandler, authDomainHandler, preferenceHandler, globalHandler, promoteHandler, flaggerHandler, dashboardModule, dashboardHandler, metricsExplorerHandler, gatewayHandler)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -87,8 +82,6 @@ func newProvider(
|
||||
dashboardHandler dashboard.Handler,
|
||||
metricsExplorerHandler metricsexplorer.Handler,
|
||||
gatewayHandler gateway.Handler,
|
||||
roleGetter role.Getter,
|
||||
roleHandler role.Handler,
|
||||
) (apiserver.APIServer, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
|
||||
router := mux.NewRouter().UseEncodedPath()
|
||||
@@ -109,11 +102,9 @@ func newProvider(
|
||||
dashboardHandler: dashboardHandler,
|
||||
metricsExplorerHandler: metricsExplorerHandler,
|
||||
gatewayHandler: gatewayHandler,
|
||||
roleGetter: roleGetter,
|
||||
roleHandler: roleHandler,
|
||||
}
|
||||
|
||||
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz, roleGetter)
|
||||
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz)
|
||||
|
||||
if err := provider.AddToRouter(router); err != nil {
|
||||
return nil, err
|
||||
@@ -171,10 +162,6 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.addRoleRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package signozapiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/handler"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (provider *provider) addRoleRoutes(router *mux.Router) error {
|
||||
if err := router.Handle("/api/v1/roles", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Create), handler.OpenAPIDef{
|
||||
ID: "CreateRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Create role",
|
||||
Description: "This endpoint creates a role",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.Identifiable),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles", handler.New(provider.authZ.AdminAccess(provider.roleHandler.List), handler.OpenAPIDef{
|
||||
ID: "ListRoles",
|
||||
Tags: []string{"role"},
|
||||
Summary: "List roles",
|
||||
Description: "This endpoint lists all roles",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*roletypes.Role, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Get), handler.OpenAPIDef{
|
||||
ID: "GetRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Get role",
|
||||
Description: "This endpoint gets a role",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(roletypes.Role),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Patch), handler.OpenAPIDef{
|
||||
ID: "PatchRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Patch role",
|
||||
Description: "This endpoint patches a role",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodPatch).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Delete), handler.OpenAPIDef{
|
||||
ID: "DeleteRole",
|
||||
Tags: []string{"role"},
|
||||
Summary: "Delete role",
|
||||
Description: "This endpoint deletes a role",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -315,22 +315,5 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v2/factor_password/forgot", handler.New(provider.authZ.OpenAccess(provider.userHandler.ForgotPassword), handler.OpenAPIDef{
|
||||
ID: "ForgotPassword",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Forgot password",
|
||||
Description: "This endpoint initiates the forgot password flow by sending a reset password email",
|
||||
Request: new(types.PostableForgotPassword),
|
||||
RequestContentType: "application/json",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,10 +16,9 @@ type AuthZ interface {
|
||||
Check(context.Context, *openfgav1.TupleKey) error
|
||||
|
||||
// CheckWithTupleCreation takes upon the responsibility for generating the tuples alongside everything Check does.
|
||||
CheckWithTupleCreation(context.Context, authtypes.Claims, valuer.UUID, authtypes.Relation, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
|
||||
CheckWithTupleCreation(context.Context, authtypes.Claims, valuer.UUID, authtypes.Relation, authtypes.Relation, authtypes.Typeable, []authtypes.Selector) error
|
||||
|
||||
// CheckWithTupleCreationWithoutClaims checks permissions for anonymous users.
|
||||
CheckWithTupleCreationWithoutClaims(context.Context, valuer.UUID, authtypes.Relation, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
|
||||
CheckWithTupleCreationWithoutClaims(context.Context, valuer.UUID, authtypes.Relation, authtypes.Relation, authtypes.Typeable, []authtypes.Selector) error
|
||||
|
||||
// Batch Check returns error when the upstream authorization server is unavailable or for all the tuples of subject (s) doesn't have relation (r) on object (o).
|
||||
BatchCheck(context.Context, []*openfgav1.TupleKey) error
|
||||
|
||||
@@ -152,17 +152,17 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "none of the subjects are allowed for requested access")
|
||||
return errors.New(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "")
|
||||
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
||||
tuples, err := authtypes.TypeableOrganization.Tuples(subject, translation, []authtypes.Selector{authtypes.MustNewSelector(authtypes.TypeOrganization, orgID.StringValue())}, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -175,13 +175,13 @@ func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims aut
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
|
||||
tuples, err := authtypes.TypeableOrganization.Tuples(subject, translation, []authtypes.Selector{authtypes.MustNewSelector(authtypes.TypeOrganization, orgID.StringValue())}, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -195,10 +195,6 @@ func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Contex
|
||||
}
|
||||
|
||||
func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
|
||||
if len(additions) == 0 && len(deletions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
storeID, modelID := provider.getStoreIDandModelID()
|
||||
deletionTuplesWithoutCondition := make([]*openfgav1.TupleKeyWithoutCondition, len(deletions))
|
||||
for idx, tuple := range deletions {
|
||||
|
||||
@@ -34,11 +34,11 @@ func TestProviderStartStop(t *testing.T) {
|
||||
sqlstore.Mock().ExpectQuery("SELECT authorization_model_id, schema_version, type, type_definition, serialized_protobuf FROM authorization_model WHERE authorization_model_id = (.+) AND store = (.+)").WithArgs("01K44QQKXR6F729W160NFCJT58", "01K3V0NTN47MPTMEV1PD5ST6ZC").WillReturnRows(modelRows)
|
||||
|
||||
sqlstore.Mock().ExpectExec("INSERT INTO authorization_model (.+) VALUES (.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
go func() {
|
||||
err := provider.Start(context.Background())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// wait for the service to start
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -21,15 +20,14 @@ type AuthZ struct {
|
||||
logger *slog.Logger
|
||||
orgGetter organization.Getter
|
||||
authzService authz.AuthZ
|
||||
roleGetter role.Getter
|
||||
}
|
||||
|
||||
func NewAuthZ(logger *slog.Logger, orgGetter organization.Getter, authzService authz.AuthZ, roleGetter role.Getter) *AuthZ {
|
||||
func NewAuthZ(logger *slog.Logger, orgGetter organization.Getter, authzService authz.AuthZ) *AuthZ {
|
||||
if logger == nil {
|
||||
panic("cannot build authz middleware, logger is empty")
|
||||
}
|
||||
|
||||
return &AuthZ{logger: logger, orgGetter: orgGetter, authzService: authzService, roleGetter: roleGetter}
|
||||
return &AuthZ{logger: logger, orgGetter: orgGetter, authzService: authzService}
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
@@ -111,10 +109,9 @@ func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithClaimsFn, roles []string) http.HandlerFunc {
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithClaimsFn) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -132,18 +129,7 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := middleware.roleGetter.ListByOrgIDAndNames(req.Context(), orgId, roles)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
roleSelectors := []authtypes.Selector{}
|
||||
for _, role := range roles {
|
||||
selectors = append(selectors, authtypes.MustNewSelector(authtypes.TypeRole, role.ID.String()))
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreation(ctx, claims, orgId, relation, typeable, selectors, roleSelectors)
|
||||
err = middleware.authzService.CheckWithTupleCreation(req.Context(), claims, orgId, relation, translation, typeable, selectors)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -153,7 +139,7 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithoutClaimsFn, roles []string) http.HandlerFunc {
|
||||
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithoutClaimsFn) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
orgs, err := middleware.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
@@ -168,7 +154,7 @@ func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation auth
|
||||
return
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, typeable, selectors, selectors)
|
||||
err = middleware.authzService.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, translation, typeable, selectors)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package implrole
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type getter struct {
|
||||
store roletypes.Store
|
||||
}
|
||||
|
||||
func NewGetter(store roletypes.Store) role.Getter {
|
||||
return &getter{store: store}
|
||||
}
|
||||
|
||||
func (getter *getter) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
|
||||
storableRole, err := getter.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roletypes.NewRoleFromStorableRole(storableRole), nil
|
||||
}
|
||||
|
||||
func (getter *getter) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.Role, error) {
|
||||
storableRole, err := getter.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roletypes.NewRoleFromStorableRole(storableRole), nil
|
||||
}
|
||||
|
||||
func (getter *getter) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
|
||||
storableRoles, err := getter.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*roletypes.Role, len(storableRoles))
|
||||
for idx, storableRole := range storableRoles {
|
||||
roles[idx] = roletypes.NewRoleFromStorableRole(storableRole)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (getter *getter) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.Role, error) {
|
||||
storableRoles, err := getter.store.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*roletypes.Role, len(storableRoles))
|
||||
for idx, storable := range storableRoles {
|
||||
roles[idx] = roletypes.NewRoleFromStorableRole(storable)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package implrole
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type granter struct {
|
||||
store roletypes.Store
|
||||
authz authz.AuthZ
|
||||
}
|
||||
|
||||
func NewGranter(store roletypes.Store, authz authz.AuthZ) role.Granter {
|
||||
return &granter{store: store, authz: authz}
|
||||
}
|
||||
|
||||
func (granter *granter) Grant(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
|
||||
role, err := granter.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := authtypes.TypeableRole.Tuples(
|
||||
subject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, role.ID.StringValue()),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return granter.authz.Write(ctx, tuples, nil)
|
||||
}
|
||||
|
||||
func (granter *granter) GrantByID(ctx context.Context, orgID valuer.UUID, id valuer.UUID, subject string) error {
|
||||
tuples, err := authtypes.TypeableRole.Tuples(
|
||||
subject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, id.StringValue()),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return granter.authz.Write(ctx, tuples, nil)
|
||||
}
|
||||
|
||||
func (granter *granter) ModifyGrant(ctx context.Context, orgID valuer.UUID, existingRoleName string, updatedRoleName string, subject string) error {
|
||||
err := granter.Revoke(ctx, orgID, existingRoleName, subject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = granter.Grant(ctx, orgID, updatedRoleName, subject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (granter *granter) Revoke(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
|
||||
role, err := granter.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := authtypes.TypeableRole.Tuples(
|
||||
subject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, role.ID.StringValue()),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return granter.authz.Write(ctx, nil, tuples)
|
||||
}
|
||||
|
||||
func (granter *granter) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*roletypes.Role) error {
|
||||
err := granter.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
for _, role := range managedRoles {
|
||||
err := granter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -14,12 +14,11 @@ import (
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
setter role.Setter
|
||||
getter role.Getter
|
||||
module role.Module
|
||||
}
|
||||
|
||||
func NewHandler(setter role.Setter, getter role.Getter) role.Handler {
|
||||
return &handler{setter: setter, getter: getter}
|
||||
func NewHandler(module role.Module) role.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -36,7 +35,7 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.setter.Create(ctx, valuer.MustNewUUID(claims.OrgID), roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID)))
|
||||
err = handler.module.Create(ctx, roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom.StringValue(), valuer.MustNewUUID(claims.OrgID)))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -64,7 +63,7 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
role, err := handler.getter.Get(ctx, valuer.MustNewUUID(claims.OrgID), roleID)
|
||||
role, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), roleID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -103,7 +102,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
objects, err := handler.setter.GetObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation)
|
||||
objects, err := handler.module.GetObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -114,7 +113,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (handler *handler) GetResources(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
resources := handler.setter.GetResources(ctx)
|
||||
resources := handler.module.GetResources(ctx)
|
||||
|
||||
var resourceRelations = struct {
|
||||
Resources []*authtypes.Resource `json:"resources"`
|
||||
@@ -134,7 +133,7 @@ func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := handler.getter.List(ctx, valuer.MustNewUUID(claims.OrgID))
|
||||
roles, err := handler.module.List(ctx, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -163,19 +162,14 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
role, err := handler.getter.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
role, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = role.PatchMetadata(req.Name, req.Description)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.setter.Patch(ctx, valuer.MustNewUUID(claims.OrgID), role)
|
||||
role.PatchMetadata(req.Name, req.Description)
|
||||
err = handler.module.Patch(ctx, valuer.MustNewUUID(claims.OrgID), role)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -210,19 +204,13 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
role, err := handler.getter.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
patchableObjects, err := roletypes.NewPatchableObjects(req.Additions, req.Deletions, relation)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
patchableObjects, err := role.NewPatchableObjects(req.Additions, req.Deletions, relation)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.setter.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), id, relation, patchableObjects.Additions, patchableObjects.Deletions)
|
||||
err = handler.module.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), id, relation, patchableObjects.Additions, patchableObjects.Deletions)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -245,7 +233,7 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.setter.Delete(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
err = handler.module.Delete(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
164
pkg/modules/role/implrole/module.go
Normal file
164
pkg/modules/role/implrole/module.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package implrole
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store roletypes.Store
|
||||
registry []role.RegisterTypeable
|
||||
authz authz.AuthZ
|
||||
}
|
||||
|
||||
func NewModule(store roletypes.Store, authz authz.AuthZ, registry []role.RegisterTypeable) role.Module {
|
||||
return &module{
|
||||
store: store,
|
||||
authz: authz,
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, role *roletypes.Role) error {
|
||||
return module.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (module *module) GetOrCreate(ctx context.Context, role *roletypes.Role) (*roletypes.Role, error) {
|
||||
existingRole, err := module.store.GetByNameAndOrgID(ctx, role.Name, role.OrgID)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if existingRole != nil {
|
||||
return roletypes.NewRoleFromStorableRole(existingRole), nil
|
||||
}
|
||||
|
||||
err = module.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (module *module) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
typeables := make([]authtypes.Typeable, 0)
|
||||
for _, register := range module.registry {
|
||||
typeables = append(typeables, register.MustGetTypeables()...)
|
||||
}
|
||||
// role module cannot self register itself!
|
||||
typeables = append(typeables, module.MustGetTypeables()...)
|
||||
|
||||
resources := make([]*authtypes.Resource, 0)
|
||||
for _, typeable := range typeables {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
|
||||
storableRole, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roletypes.NewRoleFromStorableRole(storableRole), nil
|
||||
}
|
||||
|
||||
func (module *module) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
storableRole, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects := make([]*authtypes.Object, 0)
|
||||
for _, resource := range module.GetResources(ctx) {
|
||||
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
|
||||
resourceObjects, err := module.
|
||||
authz.
|
||||
ListObjects(
|
||||
ctx,
|
||||
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
|
||||
relation,
|
||||
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects = append(objects, resourceObjects...)
|
||||
}
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
|
||||
storableRoles, err := module.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*roletypes.Role, len(storableRoles))
|
||||
for idx, storableRole := range storableRoles {
|
||||
roles[idx] = roletypes.NewRoleFromStorableRole(storableRole)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (module *module) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
return module.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (module *module) PatchObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
|
||||
additionTuples, err := roletypes.GetAdditionTuples(id, orgID, relation, additions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionTuples, err := roletypes.GetDeletionTuples(id, orgID, relation, deletions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.authz.Write(ctx, additionTuples, deletionTuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) Assign(ctx context.Context, id valuer.UUID, orgID valuer.UUID, subject string) error {
|
||||
tuples, err := authtypes.TypeableRole.Tuples(
|
||||
subject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, id.StringValue()),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return module.authz.Write(ctx, tuples, nil)
|
||||
}
|
||||
|
||||
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package implrole
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type setter struct {
|
||||
store roletypes.Store
|
||||
authz authz.AuthZ
|
||||
}
|
||||
|
||||
func NewSetter(store roletypes.Store, authz authz.AuthZ) role.Setter {
|
||||
return &setter{store: store, authz: authz}
|
||||
}
|
||||
|
||||
func (setter *setter) Create(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (setter *setter) GetOrCreate(_ context.Context, _ valuer.UUID, _ *roletypes.Role) (*roletypes.Role, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (setter *setter) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (setter *setter) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (setter *setter) Patch(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (setter *setter) PatchObjects(_ context.Context, _ valuer.UUID, _ valuer.UUID, _ authtypes.Relation, _, _ []*authtypes.Object) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (setter *setter) Delete(_ context.Context, _ valuer.UUID, _ valuer.UUID) error {
|
||||
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (setter *setter) MustGetTypeables() []authtypes.Typeable {
|
||||
return nil
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
@@ -21,7 +20,7 @@ func NewStore(sqlstore sqlstore.SQLStore) roletypes.Store {
|
||||
func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
BunDB().
|
||||
NewInsert().
|
||||
Model(role).
|
||||
Exec(ctx)
|
||||
@@ -36,7 +35,7 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
role := new(roletypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(role).
|
||||
Where("org_id = ?", orgID).
|
||||
@@ -49,11 +48,11 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.StorableRole, error) {
|
||||
func (store *store) GetByNameAndOrgID(ctx context.Context, name string, orgID valuer.UUID) (*roletypes.StorableRole, error) {
|
||||
role := new(roletypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(role).
|
||||
Where("org_id = ?", orgID).
|
||||
@@ -70,30 +69,13 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.S
|
||||
roles := make([]*roletypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&roles).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.StorableRole, error) {
|
||||
roles := make([]*roletypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&roles).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("name IN (?)", bun.In(names)).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "no roles found in org_id: %s", orgID)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
@@ -102,7 +84,7 @@ func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID,
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(role).
|
||||
WherePK().
|
||||
@@ -118,7 +100,7 @@ func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletyp
|
||||
func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
BunDB().
|
||||
NewDelete().
|
||||
Model(new(roletypes.StorableRole)).
|
||||
Where("org_id = ?", orgID).
|
||||
|
||||
@@ -9,16 +9,22 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Setter interface {
|
||||
type Module interface {
|
||||
// Creates the role.
|
||||
Create(context.Context, valuer.UUID, *roletypes.Role) error
|
||||
Create(context.Context, *roletypes.Role) error
|
||||
|
||||
// Gets the role if it exists or creates one.
|
||||
GetOrCreate(context.Context, valuer.UUID, *roletypes.Role) (*roletypes.Role, error)
|
||||
GetOrCreate(context.Context, *roletypes.Role) (*roletypes.Role, error)
|
||||
|
||||
// Gets the role
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
|
||||
|
||||
// Gets the objects associated with the given role and relation.
|
||||
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error)
|
||||
|
||||
// Lists all the roles for the organization.
|
||||
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
|
||||
|
||||
// Gets all the typeable resources registered from role registry.
|
||||
GetResources(context.Context) []*authtypes.Resource
|
||||
|
||||
@@ -31,40 +37,12 @@ type Setter interface {
|
||||
// Deletes the role and tuples in authorization server.
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
// Assigns role to the given subject.
|
||||
Assign(context.Context, valuer.UUID, valuer.UUID, string) error
|
||||
|
||||
RegisterTypeable
|
||||
}
|
||||
|
||||
type Getter interface {
|
||||
// Gets the role
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
|
||||
|
||||
// Gets the role by org_id and name
|
||||
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*roletypes.Role, error)
|
||||
|
||||
// Lists all the roles for the organization.
|
||||
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
|
||||
|
||||
// Lists all the roles for the organization filtered by name
|
||||
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*roletypes.Role, error)
|
||||
}
|
||||
|
||||
type Granter interface {
|
||||
// Grants a role to the subject based on role name.
|
||||
Grant(context.Context, valuer.UUID, string, string) error
|
||||
|
||||
// Grants a role to the subject based on role id.
|
||||
GrantByID(context.Context, valuer.UUID, valuer.UUID, string) error
|
||||
|
||||
// Revokes a granted role from the subject based on role name.
|
||||
Revoke(context.Context, valuer.UUID, string, string) error
|
||||
|
||||
// Changes the granted role for the subject based on role name.
|
||||
ModifyGrant(context.Context, valuer.UUID, string, string, string) error
|
||||
|
||||
// Bootstrap the managed roles.
|
||||
CreateManagedRoles(context.Context, valuer.UUID, []*roletypes.Role) error
|
||||
}
|
||||
|
||||
type RegisterTypeable interface {
|
||||
MustGetTypeables() []authtypes.Typeable
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Password PasswordConfig `mapstructure:"password"`
|
||||
}
|
||||
type PasswordConfig struct {
|
||||
Reset ResetConfig `mapstructure:"reset"`
|
||||
}
|
||||
|
||||
type ResetConfig struct {
|
||||
AllowSelf bool `mapstructure:"allow_self"`
|
||||
MaxTokenLifetime time.Duration `mapstructure:"max_token_lifetime"`
|
||||
}
|
||||
|
||||
func NewConfigFactory() factory.ConfigFactory {
|
||||
return factory.NewConfigFactory(factory.MustNewName("user"), newConfig)
|
||||
}
|
||||
|
||||
func newConfig() factory.Config {
|
||||
return &Config{
|
||||
Password: PasswordConfig{
|
||||
Reset: ResetConfig{
|
||||
AllowSelf: false,
|
||||
MaxTokenLifetime: 6 * time.Hour,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if c.Password.Reset.MaxTokenLifetime <= 0 {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::password::reset::max_token_lifetime must be positive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -332,25 +332,6 @@ func (handler *handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
render.Success(w, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (h *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := new(types.PostableForgotPassword)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.module.ForgotPassword(ctx, req.OrgID, req.Email, req.FrontendBaseURL)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (h *handler) CreateAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -12,14 +12,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
root "github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/tokenizer"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/emailtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/dustin/go-humanize"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
@@ -30,13 +27,11 @@ type Module struct {
|
||||
emailing emailing.Emailing
|
||||
settings factory.ScopedProviderSettings
|
||||
orgSetter organization.Setter
|
||||
granter role.Granter
|
||||
analytics analytics.Analytics
|
||||
config user.Config
|
||||
}
|
||||
|
||||
// This module is a WIP, don't take inspiration from this.
|
||||
func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, granter role.Granter, 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, analytics analytics.Analytics) root.Module {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
|
||||
return &Module{
|
||||
store: store,
|
||||
@@ -45,8 +40,6 @@ func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing em
|
||||
settings: settings,
|
||||
orgSetter: orgSetter,
|
||||
analytics: analytics,
|
||||
granter: granter,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +223,7 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
}
|
||||
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
updatedUser, err := m.store.UpdateUser(ctx, orgID, id, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -260,8 +254,8 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
return updatedUser, nil
|
||||
}
|
||||
|
||||
func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error {
|
||||
user, err := module.store.GetUser(ctx, valuer.MustNewUUID(id))
|
||||
func (m *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error {
|
||||
user, err := m.store.GetUser(ctx, valuer.MustNewUUID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -271,7 +265,7 @@ 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.GetUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
|
||||
adminUsers, err := m.store.GetUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -280,11 +274,11 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
|
||||
}
|
||||
|
||||
if err := module.store.DeleteUser(ctx, orgID.String(), user.ID.StringValue()); err != nil {
|
||||
if err := m.store.DeleteUser(ctx, orgID.String(), user.ID.StringValue()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Deleted", map[string]any{
|
||||
m.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Deleted", map[string]any{
|
||||
"deleted_by": deletedBy,
|
||||
})
|
||||
|
||||
@@ -308,91 +302,33 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
|
||||
}
|
||||
}
|
||||
|
||||
// check if a token already exists for this password id
|
||||
existingResetPasswordToken, err := module.store.GetResetPasswordTokenByPasswordID(ctx, password.ID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err // return the error if it is not a not found error
|
||||
resetPasswordToken, err := types.NewResetPasswordToken(password.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return the existing token if it is not expired
|
||||
if existingResetPasswordToken != nil && !existingResetPasswordToken.IsExpired() {
|
||||
return existingResetPasswordToken, nil // return the existing token if it is not expired
|
||||
}
|
||||
err = module.store.CreateResetPasswordToken(ctx, resetPasswordToken)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeAlreadyExists) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// delete the existing token entry
|
||||
if existingResetPasswordToken != nil {
|
||||
if err := module.store.DeleteResetPasswordTokenByPasswordID(ctx, password.ID); err != nil {
|
||||
// if the token already exists, we return the existing token
|
||||
resetPasswordToken, err = module.store.GetResetPasswordTokenByPasswordID(ctx, password.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// create a new token
|
||||
resetPasswordToken, err := types.NewResetPasswordToken(password.ID, time.Now().Add(module.config.Password.Reset.MaxTokenLifetime))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create a new token
|
||||
err = module.store.CreateResetPasswordToken(ctx, resetPasswordToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resetPasswordToken, nil
|
||||
}
|
||||
|
||||
func (module *Module) ForgotPassword(ctx context.Context, orgID valuer.UUID, email valuer.Email, frontendBaseURL string) error {
|
||||
if !module.config.Password.Reset.AllowSelf {
|
||||
return errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "users are not allowed to reset their password themselves, please contact an admin to reset your password")
|
||||
}
|
||||
|
||||
user, err := module.store.GetUserByEmailAndOrgID(ctx, email, orgID)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil // for security reasons
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := module.GetOrCreateResetPasswordToken(ctx, user.ID)
|
||||
if err != nil {
|
||||
module.settings.Logger().ErrorContext(ctx, "failed to create reset password token", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
resetLink := fmt.Sprintf("%s/password-reset?token=%s", frontendBaseURL, token.Token)
|
||||
|
||||
tokenLifetime := module.config.Password.Reset.MaxTokenLifetime
|
||||
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
|
||||
|
||||
if err := module.emailing.SendHTML(
|
||||
ctx,
|
||||
user.Email.String(),
|
||||
"Reset your SigNoz password",
|
||||
emailtypes.TemplateNameResetPassword,
|
||||
map[string]any{
|
||||
"Name": user.DisplayName,
|
||||
"Link": resetLink,
|
||||
"Expiry": humanizedTokenLifetime,
|
||||
},
|
||||
); err != nil {
|
||||
module.settings.Logger().ErrorContext(ctx, "failed to send reset password email", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, token string, passwd string) error {
|
||||
resetPasswordToken, err := module.store.GetResetPasswordToken(ctx, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resetPasswordToken.IsExpired() {
|
||||
return errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "reset password token has expired")
|
||||
}
|
||||
|
||||
password, err := module.store.GetPassword(ctx, resetPasswordToken.PasswordID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -478,7 +414,7 @@ func (module *Module) CreateFirstUser(ctx context.Context, organization *types.O
|
||||
}
|
||||
|
||||
if err = module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
err = module.orgSetter.Create(ctx, organization)
|
||||
err := module.orgSetter.Create(ctx, organization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -391,18 +391,6 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
|
||||
return resetPasswordToken, nil
|
||||
}
|
||||
|
||||
func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) error {
|
||||
_, err := store.sqlstore.BunDB().NewDelete().
|
||||
Model(&types.ResetPasswordToken{}).
|
||||
Where("password_id = ?", passwordID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete reset password token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*types.ResetPasswordToken, error) {
|
||||
resetPasswordRequest := new(types.ResetPasswordToken)
|
||||
|
||||
|
||||
@@ -30,9 +30,6 @@ type Module interface {
|
||||
// Updates password of user to the new password. It also deletes all reset password tokens for the user.
|
||||
UpdatePassword(ctx context.Context, userID valuer.UUID, oldPassword string, password string) error
|
||||
|
||||
// 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 *types.User, updatedBy string) (*types.User, error)
|
||||
DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error
|
||||
|
||||
@@ -95,7 +92,6 @@ type Handler interface {
|
||||
GetResetPasswordToken(http.ResponseWriter, *http.Request)
|
||||
ResetPassword(http.ResponseWriter, *http.Request)
|
||||
ChangePassword(http.ResponseWriter, *http.Request)
|
||||
ForgotPassword(http.ResponseWriter, *http.Request)
|
||||
|
||||
// API KEY
|
||||
CreateAPIKey(http.ResponseWriter, *http.Request)
|
||||
|
||||
@@ -36,6 +36,29 @@ func NewBucketCache(settings factory.ProviderSettings, cache cache.Cache, cacheT
|
||||
}
|
||||
}
|
||||
|
||||
// cachedBucket represents a cached time bucket
|
||||
type cachedBucket struct {
|
||||
StartMs uint64 `json:"startMs"`
|
||||
EndMs uint64 `json:"endMs"`
|
||||
Type qbtypes.RequestType `json:"type"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
Stats qbtypes.ExecStats `json:"stats"`
|
||||
}
|
||||
|
||||
// cachedData represents the full cached data for a query
|
||||
type cachedData struct {
|
||||
Buckets []*cachedBucket `json:"buckets"`
|
||||
Warnings []string `json:"warnings"`
|
||||
}
|
||||
|
||||
func (c *cachedData) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, c)
|
||||
}
|
||||
|
||||
func (c *cachedData) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// GetMissRanges returns cached data and missing time ranges
|
||||
func (bc *bucketCache) GetMissRanges(
|
||||
ctx context.Context,
|
||||
@@ -55,7 +78,7 @@ func (bc *bucketCache) GetMissRanges(
|
||||
bc.logger.DebugContext(ctx, "cache key", "cache_key", cacheKey)
|
||||
|
||||
// Try to get cached data
|
||||
var data qbtypes.CachedData
|
||||
var data cachedData
|
||||
err := bc.cache.Get(ctx, orgID, cacheKey, &data)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
@@ -124,9 +147,9 @@ func (bc *bucketCache) Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Que
|
||||
cacheKey := bc.generateCacheKey(q)
|
||||
|
||||
// Get existing cached data
|
||||
var existingData qbtypes.CachedData
|
||||
var existingData cachedData
|
||||
if err := bc.cache.Get(ctx, orgID, cacheKey, &existingData); err != nil {
|
||||
existingData = qbtypes.CachedData{}
|
||||
existingData = cachedData{}
|
||||
}
|
||||
|
||||
// Trim the result to exclude data within flux interval
|
||||
@@ -180,7 +203,7 @@ func (bc *bucketCache) Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Que
|
||||
uniqueWarnings := bc.deduplicateWarnings(allWarnings)
|
||||
|
||||
// Create updated cached data
|
||||
updatedData := qbtypes.CachedData{
|
||||
updatedData := cachedData{
|
||||
Buckets: mergedBuckets,
|
||||
Warnings: uniqueWarnings,
|
||||
}
|
||||
@@ -199,7 +222,7 @@ func (bc *bucketCache) generateCacheKey(q qbtypes.Query) string {
|
||||
}
|
||||
|
||||
// findMissingRangesWithStep identifies time ranges not covered by cached buckets with step alignment
|
||||
func (bc *bucketCache) findMissingRangesWithStep(buckets []*qbtypes.CachedBucket, startMs, endMs uint64, stepMs uint64) []*qbtypes.TimeRange {
|
||||
func (bc *bucketCache) findMissingRangesWithStep(buckets []*cachedBucket, startMs, endMs uint64, stepMs uint64) []*qbtypes.TimeRange {
|
||||
// When step is 0 or window is too small to be cached, use simple algorithm
|
||||
if stepMs == 0 || (startMs+stepMs) > endMs {
|
||||
return bc.findMissingRangesBasic(buckets, startMs, endMs)
|
||||
@@ -242,7 +265,7 @@ func (bc *bucketCache) findMissingRangesWithStep(buckets []*qbtypes.CachedBucket
|
||||
}
|
||||
|
||||
if needsSort {
|
||||
slices.SortFunc(buckets, func(a, b *qbtypes.CachedBucket) int {
|
||||
slices.SortFunc(buckets, func(a, b *cachedBucket) int {
|
||||
if a.StartMs < b.StartMs {
|
||||
return -1
|
||||
}
|
||||
@@ -316,7 +339,7 @@ func (bc *bucketCache) findMissingRangesWithStep(buckets []*qbtypes.CachedBucket
|
||||
}
|
||||
|
||||
// findMissingRangesBasic is the simple algorithm without step alignment
|
||||
func (bc *bucketCache) findMissingRangesBasic(buckets []*qbtypes.CachedBucket, startMs, endMs uint64) []*qbtypes.TimeRange {
|
||||
func (bc *bucketCache) findMissingRangesBasic(buckets []*cachedBucket, startMs, endMs uint64) []*qbtypes.TimeRange {
|
||||
// Check if already sorted before sorting
|
||||
needsSort := false
|
||||
for i := 1; i < len(buckets); i++ {
|
||||
@@ -327,7 +350,7 @@ func (bc *bucketCache) findMissingRangesBasic(buckets []*qbtypes.CachedBucket, s
|
||||
}
|
||||
|
||||
if needsSort {
|
||||
slices.SortFunc(buckets, func(a, b *qbtypes.CachedBucket) int {
|
||||
slices.SortFunc(buckets, func(a, b *cachedBucket) int {
|
||||
if a.StartMs < b.StartMs {
|
||||
return -1
|
||||
}
|
||||
@@ -398,9 +421,9 @@ func (bc *bucketCache) findMissingRangesBasic(buckets []*qbtypes.CachedBucket, s
|
||||
}
|
||||
|
||||
// filterRelevantBuckets returns buckets that overlap with the requested time range
|
||||
func (bc *bucketCache) filterRelevantBuckets(buckets []*qbtypes.CachedBucket, startMs, endMs uint64) []*qbtypes.CachedBucket {
|
||||
func (bc *bucketCache) filterRelevantBuckets(buckets []*cachedBucket, startMs, endMs uint64) []*cachedBucket {
|
||||
// Pre-allocate with estimated capacity
|
||||
relevant := make([]*qbtypes.CachedBucket, 0, len(buckets))
|
||||
relevant := make([]*cachedBucket, 0, len(buckets))
|
||||
|
||||
for _, bucket := range buckets {
|
||||
// Check if bucket overlaps with requested range
|
||||
@@ -410,7 +433,7 @@ func (bc *bucketCache) filterRelevantBuckets(buckets []*qbtypes.CachedBucket, st
|
||||
}
|
||||
|
||||
// Sort by start time
|
||||
slices.SortFunc(relevant, func(a, b *qbtypes.CachedBucket) int {
|
||||
slices.SortFunc(relevant, func(a, b *cachedBucket) int {
|
||||
if a.StartMs < b.StartMs {
|
||||
return -1
|
||||
}
|
||||
@@ -424,7 +447,7 @@ func (bc *bucketCache) filterRelevantBuckets(buckets []*qbtypes.CachedBucket, st
|
||||
}
|
||||
|
||||
// mergeBuckets combines multiple cached buckets into a single result
|
||||
func (bc *bucketCache) mergeBuckets(ctx context.Context, buckets []*qbtypes.CachedBucket, warnings []string) *qbtypes.Result {
|
||||
func (bc *bucketCache) mergeBuckets(ctx context.Context, buckets []*cachedBucket, warnings []string) *qbtypes.Result {
|
||||
if len(buckets) == 0 {
|
||||
return &qbtypes.Result{}
|
||||
}
|
||||
@@ -457,7 +480,7 @@ func (bc *bucketCache) mergeBuckets(ctx context.Context, buckets []*qbtypes.Cach
|
||||
}
|
||||
|
||||
// mergeTimeSeriesValues merges time series data from multiple buckets
|
||||
func (bc *bucketCache) mergeTimeSeriesValues(ctx context.Context, buckets []*qbtypes.CachedBucket) *qbtypes.TimeSeriesData {
|
||||
func (bc *bucketCache) mergeTimeSeriesValues(ctx context.Context, buckets []*cachedBucket) *qbtypes.TimeSeriesData {
|
||||
// Estimate capacity based on bucket count
|
||||
estimatedSeries := len(buckets) * 10
|
||||
|
||||
@@ -608,7 +631,7 @@ func (bc *bucketCache) isEmptyResult(result *qbtypes.Result) (isEmpty bool, isFi
|
||||
}
|
||||
|
||||
// resultToBuckets converts a query result into time-based buckets
|
||||
func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Result, startMs, endMs uint64) []*qbtypes.CachedBucket {
|
||||
func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Result, startMs, endMs uint64) []*cachedBucket {
|
||||
// Check if result is empty
|
||||
isEmpty, isFiltered := bc.isEmptyResult(result)
|
||||
|
||||
@@ -629,7 +652,7 @@ func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Resu
|
||||
|
||||
// Always create a bucket, even for empty filtered results
|
||||
// This ensures we don't re-query for data that doesn't exist
|
||||
return []*qbtypes.CachedBucket{
|
||||
return []*cachedBucket{
|
||||
{
|
||||
StartMs: startMs,
|
||||
EndMs: endMs,
|
||||
@@ -641,9 +664,9 @@ func (bc *bucketCache) resultToBuckets(ctx context.Context, result *qbtypes.Resu
|
||||
}
|
||||
|
||||
// mergeAndDeduplicateBuckets combines and deduplicates bucket lists
|
||||
func (bc *bucketCache) mergeAndDeduplicateBuckets(existing, fresh []*qbtypes.CachedBucket) []*qbtypes.CachedBucket {
|
||||
func (bc *bucketCache) mergeAndDeduplicateBuckets(existing, fresh []*cachedBucket) []*cachedBucket {
|
||||
// Create a map to deduplicate by time range
|
||||
bucketMap := make(map[string]*qbtypes.CachedBucket)
|
||||
bucketMap := make(map[string]*cachedBucket)
|
||||
|
||||
// Add existing buckets
|
||||
for _, bucket := range existing {
|
||||
@@ -658,13 +681,13 @@ func (bc *bucketCache) mergeAndDeduplicateBuckets(existing, fresh []*qbtypes.Cac
|
||||
}
|
||||
|
||||
// Convert back to slice with pre-allocated capacity
|
||||
result := make([]*qbtypes.CachedBucket, 0, len(bucketMap))
|
||||
result := make([]*cachedBucket, 0, len(bucketMap))
|
||||
for _, bucket := range bucketMap {
|
||||
result = append(result, bucket)
|
||||
}
|
||||
|
||||
// Sort by start time
|
||||
slices.SortFunc(result, func(a, b *qbtypes.CachedBucket) int {
|
||||
slices.SortFunc(result, func(a, b *cachedBucket) int {
|
||||
if a.StartMs < b.StartMs {
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -147,14 +147,14 @@ func BenchmarkBucketCache_MergeTimeSeriesValues(b *testing.B) {
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
// Create test buckets
|
||||
buckets := make([]*qbtypes.CachedBucket, tc.numBuckets)
|
||||
buckets := make([]*cachedBucket, tc.numBuckets)
|
||||
for i := 0; i < tc.numBuckets; i++ {
|
||||
startMs := uint64(i * 10000)
|
||||
endMs := uint64((i + 1) * 10000)
|
||||
result := createBenchmarkResultWithSeries(startMs, endMs, 1000, tc.numSeries, tc.numValues)
|
||||
|
||||
valueBytes, _ := json.Marshal(result.Value)
|
||||
buckets[i] = &qbtypes.CachedBucket{
|
||||
buckets[i] = &cachedBucket{
|
||||
StartMs: startMs,
|
||||
EndMs: endMs,
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
@@ -417,8 +417,8 @@ func createBenchmarkResultWithSeries(startMs, endMs uint64, _ uint64, numSeries,
|
||||
}
|
||||
|
||||
// Helper function to create buckets with specific gap patterns
|
||||
func createBucketsWithPattern(numBuckets int, pattern string) []*qbtypes.CachedBucket {
|
||||
buckets := make([]*qbtypes.CachedBucket, 0, numBuckets)
|
||||
func createBucketsWithPattern(numBuckets int, pattern string) []*cachedBucket {
|
||||
buckets := make([]*cachedBucket, 0, numBuckets)
|
||||
|
||||
for i := 0; i < numBuckets; i++ {
|
||||
// Skip some buckets based on pattern
|
||||
@@ -432,7 +432,7 @@ func createBucketsWithPattern(numBuckets int, pattern string) []*qbtypes.CachedB
|
||||
startMs := uint64(i * 10000)
|
||||
endMs := uint64((i + 1) * 10000)
|
||||
|
||||
buckets = append(buckets, &qbtypes.CachedBucket{
|
||||
buckets = append(buckets, &cachedBucket{
|
||||
StartMs: startMs,
|
||||
EndMs: endMs,
|
||||
Type: qbtypes.RequestTypeTimeSeries,
|
||||
|
||||
@@ -521,7 +521,7 @@ func TestBucketCache_FindMissingRanges_EdgeCases(t *testing.T) {
|
||||
bc := NewBucketCache(instrumentationtest.New().ToProviderSettings(), memCache, cacheTTL, defaultFluxInterval).(*bucketCache)
|
||||
|
||||
// Test with buckets that have gaps and overlaps
|
||||
buckets := []*qbtypes.CachedBucket{
|
||||
buckets := []*cachedBucket{
|
||||
{StartMs: 1000, EndMs: 2000},
|
||||
{StartMs: 2500, EndMs: 3500},
|
||||
{StartMs: 3000, EndMs: 4000}, // Overlaps with previous
|
||||
@@ -1097,7 +1097,7 @@ func TestBucketCache_FindMissingRangesWithStep(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
buckets []*qbtypes.CachedBucket
|
||||
buckets []*cachedBucket
|
||||
startMs uint64
|
||||
endMs uint64
|
||||
stepMs uint64
|
||||
@@ -1106,7 +1106,7 @@ func TestBucketCache_FindMissingRangesWithStep(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "start_not_aligned_to_step",
|
||||
buckets: []*qbtypes.CachedBucket{},
|
||||
buckets: []*cachedBucket{},
|
||||
startMs: 1500, // Not aligned to 1000ms step
|
||||
endMs: 5000,
|
||||
stepMs: 1000,
|
||||
@@ -1118,7 +1118,7 @@ func TestBucketCache_FindMissingRangesWithStep(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "end_not_aligned_to_step",
|
||||
buckets: []*qbtypes.CachedBucket{},
|
||||
buckets: []*cachedBucket{},
|
||||
startMs: 1000,
|
||||
endMs: 4500, // Not aligned to 1000ms step
|
||||
stepMs: 1000,
|
||||
@@ -1129,7 +1129,7 @@ func TestBucketCache_FindMissingRangesWithStep(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "bucket_boundaries_not_aligned",
|
||||
buckets: []*qbtypes.CachedBucket{
|
||||
buckets: []*cachedBucket{
|
||||
{StartMs: 1500, EndMs: 2500}, // Not aligned
|
||||
},
|
||||
startMs: 1000,
|
||||
@@ -1143,7 +1143,7 @@ func TestBucketCache_FindMissingRangesWithStep(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "small_window_less_than_step",
|
||||
buckets: []*qbtypes.CachedBucket{},
|
||||
buckets: []*cachedBucket{},
|
||||
startMs: 1000,
|
||||
endMs: 1500, // Less than one step
|
||||
stepMs: 1000,
|
||||
@@ -1154,7 +1154,7 @@ func TestBucketCache_FindMissingRangesWithStep(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "zero_step_uses_basic_algorithm",
|
||||
buckets: []*qbtypes.CachedBucket{},
|
||||
buckets: []*cachedBucket{},
|
||||
startMs: 1000,
|
||||
endMs: 5000,
|
||||
stepMs: 0,
|
||||
|
||||
@@ -209,7 +209,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
||||
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||
r.Use(middleware.NewComment().Wrap)
|
||||
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz, s.signoz.Modules.RoleGetter)
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz)
|
||||
|
||||
api.RegisterRoutes(r, am)
|
||||
api.RegisterLogsRoutes(r, am)
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/ruler"
|
||||
@@ -110,9 +109,6 @@ type Config struct {
|
||||
|
||||
// Flagger config
|
||||
Flagger flagger.Config `mapstructure:"flagger"`
|
||||
|
||||
// User config
|
||||
User user.Config `mapstructure:"user"`
|
||||
}
|
||||
|
||||
// DeprecatedFlags are the flags that are deprecated and scheduled for removal.
|
||||
@@ -175,7 +171,6 @@ func NewConfig(ctx context.Context, logger *slog.Logger, resolverConfig config.R
|
||||
tokenizer.NewConfigFactory(),
|
||||
metricsexplorer.NewConfigFactory(),
|
||||
flagger.NewConfigFactory(),
|
||||
user.NewConfigFactory(),
|
||||
}
|
||||
|
||||
conf, err := config.New(ctx, resolverConfig, configFactories)
|
||||
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/services"
|
||||
@@ -43,7 +41,6 @@ type Handlers struct {
|
||||
Global global.Handler
|
||||
FlaggerHandler flagger.Handler
|
||||
GatewayHandler gateway.Handler
|
||||
Role role.Handler
|
||||
}
|
||||
|
||||
func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing, global global.Global, flaggerService flagger.Flagger, gatewayService gateway.Gateway) Handlers {
|
||||
@@ -60,6 +57,5 @@ func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, que
|
||||
Global: signozglobal.NewHandler(global),
|
||||
FlaggerHandler: flagger.NewHandler(flaggerService),
|
||||
GatewayHandler: gateway.NewHandler(gatewayService),
|
||||
Role: implrole.NewHandler(modules.RoleSetter, modules.RoleGetter),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory/factorytest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
"github.com/SigNoz/signoz/pkg/sharder/noopsharder"
|
||||
@@ -41,10 +40,7 @@ func TestNewHandlers(t *testing.T) {
|
||||
queryParser := queryparser.New(providerSettings)
|
||||
require.NoError(t, err)
|
||||
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
|
||||
roleSetter := implrole.NewSetter(implrole.NewStore(sqlstore), nil)
|
||||
roleGetter := implrole.NewGetter(implrole.NewStore(sqlstore))
|
||||
grantModule := implrole.NewGranter(implrole.NewStore(sqlstore), nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, roleSetter, roleGetter, grantModule)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule)
|
||||
|
||||
handlers := NewHandlers(modules, providerSettings, nil, nil, nil, nil, nil)
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/services"
|
||||
@@ -67,9 +66,6 @@ type Modules struct {
|
||||
SpanPercentile spanpercentile.Module
|
||||
MetricsExplorer metricsexplorer.Module
|
||||
Promote promote.Module
|
||||
RoleSetter role.Setter
|
||||
RoleGetter role.Getter
|
||||
Granter role.Granter
|
||||
}
|
||||
|
||||
func NewModules(
|
||||
@@ -89,13 +85,10 @@ func NewModules(
|
||||
queryParser queryparser.QueryParser,
|
||||
config Config,
|
||||
dashboard dashboard.Module,
|
||||
roleSetter role.Setter,
|
||||
roleGetter role.Getter,
|
||||
granter role.Granter,
|
||||
) Modules {
|
||||
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
|
||||
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, granter, analytics, config.User)
|
||||
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, analytics)
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
|
||||
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
|
||||
|
||||
@@ -117,8 +110,5 @@ func NewModules(
|
||||
Services: implservices.NewModule(querier, telemetryStore),
|
||||
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer),
|
||||
Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore),
|
||||
RoleSetter: roleSetter,
|
||||
RoleGetter: roleGetter,
|
||||
Granter: granter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory/factorytest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
"github.com/SigNoz/signoz/pkg/sharder/noopsharder"
|
||||
@@ -41,10 +40,7 @@ func TestNewModules(t *testing.T) {
|
||||
queryParser := queryparser.New(providerSettings)
|
||||
require.NoError(t, err)
|
||||
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
|
||||
roleSetter := implrole.NewSetter(implrole.NewStore(sqlstore), nil)
|
||||
roleGetter := implrole.NewGetter(implrole.NewStore(sqlstore))
|
||||
grantModule := implrole.NewGranter(implrole.NewStore(sqlstore), nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, roleSetter, roleGetter, grantModule)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule)
|
||||
|
||||
reflectVal := reflect.ValueOf(modules)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/promote"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
@@ -50,8 +49,6 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
|
||||
struct{ dashboard.Handler }{},
|
||||
struct{ metricsexplorer.Handler }{},
|
||||
struct{ gateway.Handler }{},
|
||||
struct{ role.Getter }{},
|
||||
struct{ role.Handler }{},
|
||||
).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -161,7 +161,6 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewUpdateUserPreferenceFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewUpdateOrgPreferenceFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewRenameOrgDomainsFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddResetPasswordTokenExpiryFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,8 +242,6 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
|
||||
handlers.Dashboard,
|
||||
handlers.MetricsExplorer,
|
||||
handlers.GatewayHandler,
|
||||
modules.RoleGetter,
|
||||
handlers.Role,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,9 +90,8 @@ func New(
|
||||
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
|
||||
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error),
|
||||
authzCallback func(context.Context, sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config],
|
||||
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, role.Setter, role.Granter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
|
||||
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, role.Module, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
|
||||
gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config],
|
||||
roleSetterCallback func(sqlstore.SQLStore, authz.AuthZ, licensing.Licensing, []role.RegisterTypeable) role.Setter,
|
||||
) (*SigNoz, error) {
|
||||
// Initialize instrumentation
|
||||
instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz")
|
||||
@@ -281,12 +280,6 @@ func New(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize user getter
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
|
||||
|
||||
// Initialize the role getter
|
||||
roleGetter := implrole.NewGetter(implrole.NewStore(sqlstore))
|
||||
|
||||
// Initialize authz
|
||||
authzProviderFactory := authzCallback(ctx, sqlstore)
|
||||
authz, err := authzProviderFactory.New(ctx, providerSettings, authz.Config{})
|
||||
@@ -294,6 +287,9 @@ func New(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize user getter
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
|
||||
|
||||
// Initialize notification manager from the available notification manager provider factories
|
||||
nfManager, err := factory.NewProviderFromNamedMap(
|
||||
ctx,
|
||||
@@ -390,10 +386,9 @@ func New(
|
||||
}
|
||||
|
||||
// Initialize all modules
|
||||
roleSetter := roleSetterCallback(sqlstore, authz, licensing, nil)
|
||||
granter := implrole.NewGranter(implrole.NewStore(sqlstore), authz)
|
||||
dashboard := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, roleSetter, granter, queryParser, querier, licensing)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, roleSetter, roleGetter, granter)
|
||||
roleModule := implrole.NewModule(implrole.NewStore(sqlstore), authz, nil)
|
||||
dashboardModule := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, roleModule, queryParser, querier, licensing)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboardModule)
|
||||
|
||||
// Initialize all handlers for the modules
|
||||
handlers := NewHandlers(modules, providerSettings, querier, licensing, global, flagger, gateway)
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type addResetPasswordTokenExpiry struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddResetPasswordTokenExpiryFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_reset_password_token_expiry"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
|
||||
return newAddResetPasswordTokenExpiry(ctx, providerSettings, config, sqlstore, sqlschema)
|
||||
})
|
||||
}
|
||||
|
||||
func newAddResetPasswordTokenExpiry(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) (SQLMigration, error) {
|
||||
return &addResetPasswordTokenExpiry{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (migration *addResetPasswordTokenExpiry) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addResetPasswordTokenExpiry) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
// get the reset_password_token table
|
||||
table, uniqueConstraints, err := migration.sqlschema.GetTable(ctx, sqlschema.TableName("reset_password_token"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add a new column `expires_at`
|
||||
column := &sqlschema.Column{
|
||||
Name: sqlschema.ColumnName("expires_at"),
|
||||
DataType: sqlschema.DataTypeTimestamp,
|
||||
Nullable: true,
|
||||
}
|
||||
|
||||
// for existing rows set
|
||||
defaultValueForExistingRows := time.Now()
|
||||
|
||||
sqls := migration.sqlschema.Operator().AddColumn(table, uniqueConstraints, column, defaultValueForExistingRows)
|
||||
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addResetPasswordTokenExpiry) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -279,7 +279,7 @@ func (c *conditionBuilder) buildSpanScopeCondition(key *telemetrytypes.Telemetry
|
||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "span scope field %s only supports '=' operator", key.Name)
|
||||
}
|
||||
|
||||
var isTrue bool
|
||||
isTrue := false
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
isTrue = v
|
||||
|
||||
@@ -24,7 +24,7 @@ var (
|
||||
typeRoleSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeAnonymousSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
typeOrganizationSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeMetaResourceSelectorRegex = regexp.MustCompile(`^(^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$|\*)$`)
|
||||
typeMetaResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
// metaresources selectors are used to select either all or none
|
||||
typeMetaResourcesSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
)
|
||||
|
||||
@@ -12,13 +12,12 @@ import (
|
||||
var (
|
||||
// Templates is a list of all the templates that are supported by the emailing service.
|
||||
// This list should be updated whenever a new template is added.
|
||||
Templates = []TemplateName{TemplateNameInvitationEmail, TemplateNameUpdateRole, TemplateNameResetPassword}
|
||||
Templates = []TemplateName{TemplateNameInvitationEmail, TemplateNameUpdateRole}
|
||||
)
|
||||
|
||||
var (
|
||||
TemplateNameInvitationEmail = TemplateName{valuer.NewString("invitation_email")}
|
||||
TemplateNameUpdateRole = TemplateName{valuer.NewString("update_role")}
|
||||
TemplateNameResetPassword = TemplateName{valuer.NewString("reset_password_email")}
|
||||
)
|
||||
|
||||
type TemplateName struct{ valuer.String }
|
||||
@@ -29,8 +28,6 @@ func NewTemplateName(name string) (TemplateName, error) {
|
||||
return TemplateNameInvitationEmail, nil
|
||||
case TemplateNameUpdateRole.StringValue():
|
||||
return TemplateNameUpdateRole, nil
|
||||
case TemplateNameResetPassword.StringValue():
|
||||
return TemplateNameResetPassword, nil
|
||||
default:
|
||||
return TemplateName{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid template name: %s", name)
|
||||
}
|
||||
|
||||
@@ -35,19 +35,12 @@ type ChangePasswordRequest struct {
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type PostableForgotPassword struct {
|
||||
OrgID valuer.UUID `json:"orgId"`
|
||||
Email valuer.Email `json:"email"`
|
||||
FrontendBaseURL string `json:"frontendBaseURL"`
|
||||
}
|
||||
|
||||
type ResetPasswordToken struct {
|
||||
bun.BaseModel `bun:"table:reset_password_token"`
|
||||
|
||||
Identifiable
|
||||
Token string `bun:"token,type:text,notnull" json:"token"`
|
||||
PasswordID valuer.UUID `bun:"password_id,type:text,notnull,unique" json:"passwordId"`
|
||||
ExpiresAt time.Time `bun:"expires_at,type:timestamptz,nullzero" json:"expiresAt"`
|
||||
}
|
||||
|
||||
type FactorPassword struct {
|
||||
@@ -143,14 +136,13 @@ func NewHashedPassword(password string) (string, error) {
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
func NewResetPasswordToken(passwordID valuer.UUID, expiresAt time.Time) (*ResetPasswordToken, error) {
|
||||
func NewResetPasswordToken(passwordID valuer.UUID) (*ResetPasswordToken, error) {
|
||||
return &ResetPasswordToken{
|
||||
Identifiable: Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
Token: valuer.GenerateUUID().String(),
|
||||
PasswordID: passwordID,
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -216,7 +208,3 @@ func (f *FactorPassword) Equals(password string) bool {
|
||||
func comparePassword(hashedPassword string, password string) bool {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) == nil
|
||||
}
|
||||
|
||||
func (r *ResetPasswordToken) IsExpired() bool {
|
||||
return r.ExpiresAt.Before(time.Now())
|
||||
}
|
||||
|
||||
@@ -553,18 +553,6 @@ func (f Function) Copy() Function {
|
||||
return c
|
||||
}
|
||||
|
||||
// Validate validates the name and args for the function
|
||||
func (f Function) Validate() error {
|
||||
if err := f.Name.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Validate args for function
|
||||
if err := f.ValidateArgs(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LimitBy struct {
|
||||
// keys to limit by
|
||||
Keys []string `json:"keys"`
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package querybuildertypesv5
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
@@ -133,44 +131,12 @@ func (q *QueryBuilderQuery[T]) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var temp Alias
|
||||
// Use UnmarshalJSONWithContext for better error messages
|
||||
if err := UnmarshalJSONWithContext(data, &temp, fmt.Sprintf("query spec for %T", q)); err != nil {
|
||||
if err := UnmarshalJSONWithContext(data, &temp, "query spec"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the decoded values back to the original struct
|
||||
*q = QueryBuilderQuery[T](temp)
|
||||
|
||||
// Nomarlize the query after unmarshaling
|
||||
q.Normalize()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Normalize normalizes all the field keys in the query
|
||||
func (q *QueryBuilderQuery[T]) Normalize() {
|
||||
|
||||
// normalize select fields
|
||||
for idx := range q.SelectFields {
|
||||
q.SelectFields[idx].Normalize()
|
||||
}
|
||||
|
||||
// normalize group by fields
|
||||
for idx := range q.GroupBy {
|
||||
q.GroupBy[idx].Normalize()
|
||||
}
|
||||
|
||||
// normalize order by fields
|
||||
for idx := range q.Order {
|
||||
q.Order[idx].Key.Normalize()
|
||||
}
|
||||
|
||||
// normalize secondary aggregations
|
||||
for idx := range q.SecondaryAggregations {
|
||||
for jdx := range q.SecondaryAggregations[idx].Order {
|
||||
q.SecondaryAggregations[idx].Order[jdx].Key.Normalize()
|
||||
}
|
||||
for jdx := range q.SecondaryAggregations[idx].GroupBy {
|
||||
q.SecondaryAggregations[idx].GroupBy[jdx].Normalize()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,653 +0,0 @@
|
||||
package querybuildertypesv5
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestQueryBuilderQuery_Copy(t *testing.T) {
|
||||
t.Run("copy with all fields populated", func(t *testing.T) {
|
||||
original := QueryBuilderQuery[TraceAggregation]{
|
||||
Name: "A",
|
||||
StepInterval: Step{Duration: 60 * time.Second},
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
Source: telemetrytypes.SourceUnspecified,
|
||||
Aggregations: []TraceAggregation{
|
||||
{
|
||||
Expression: "count()",
|
||||
Alias: "trace_count",
|
||||
},
|
||||
},
|
||||
Disabled: false,
|
||||
Filter: &Filter{
|
||||
Expression: "service.name = 'frontend'",
|
||||
},
|
||||
GroupBy: []GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "service.name",
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
},
|
||||
},
|
||||
},
|
||||
Order: []OrderBy{
|
||||
{
|
||||
Key: OrderByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "timestamp",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
Direction: OrderDirectionDesc,
|
||||
},
|
||||
},
|
||||
SelectFields: []telemetrytypes.TelemetryFieldKey{
|
||||
{
|
||||
Name: "trace_id",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
Limit: 100,
|
||||
Offset: 0,
|
||||
Cursor: "cursor123",
|
||||
LimitBy: &LimitBy{
|
||||
Value: "10",
|
||||
Keys: []string{
|
||||
"service.name",
|
||||
},
|
||||
},
|
||||
Having: &Having{
|
||||
Expression: "count() > 100",
|
||||
},
|
||||
SecondaryAggregations: []SecondaryAggregation{
|
||||
{
|
||||
Limit: 10,
|
||||
Order: []OrderBy{
|
||||
{
|
||||
Key: OrderByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "value",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
Direction: OrderDirectionAsc,
|
||||
},
|
||||
},
|
||||
GroupBy: []GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "region",
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Functions: []Function{
|
||||
{
|
||||
Name: FunctionNameTimeShift,
|
||||
Args: []FunctionArg{
|
||||
{
|
||||
Name: "shift",
|
||||
Value: "1h",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Legend: "{{service.name}}",
|
||||
ShiftBy: 3600000,
|
||||
}
|
||||
|
||||
// Create a copy
|
||||
copied := original.Copy()
|
||||
|
||||
// Assert that values are equal
|
||||
assert.Equal(t, original.Name, copied.Name)
|
||||
assert.Equal(t, original.StepInterval, copied.StepInterval)
|
||||
assert.Equal(t, original.Signal, copied.Signal)
|
||||
assert.Equal(t, original.Source, copied.Source)
|
||||
assert.Equal(t, original.Disabled, copied.Disabled)
|
||||
assert.Equal(t, original.Limit, copied.Limit)
|
||||
assert.Equal(t, original.Offset, copied.Offset)
|
||||
assert.Equal(t, original.Cursor, copied.Cursor)
|
||||
assert.Equal(t, original.Legend, copied.Legend)
|
||||
assert.Equal(t, original.ShiftBy, copied.ShiftBy)
|
||||
|
||||
// Assert deep copies for slices and pointers
|
||||
require.NotNil(t, copied.Aggregations)
|
||||
assert.Equal(t, len(original.Aggregations), len(copied.Aggregations))
|
||||
assert.Equal(t, original.Aggregations[0].Expression, copied.Aggregations[0].Expression)
|
||||
|
||||
require.NotNil(t, copied.Filter)
|
||||
assert.Equal(t, original.Filter.Expression, copied.Filter.Expression)
|
||||
|
||||
require.NotNil(t, copied.GroupBy)
|
||||
assert.Equal(t, len(original.GroupBy), len(copied.GroupBy))
|
||||
assert.Equal(t, original.GroupBy[0].Name, copied.GroupBy[0].Name)
|
||||
|
||||
require.NotNil(t, copied.Order)
|
||||
assert.Equal(t, len(original.Order), len(copied.Order))
|
||||
assert.Equal(t, original.Order[0].Key.Name, copied.Order[0].Key.Name)
|
||||
|
||||
require.NotNil(t, copied.SelectFields)
|
||||
assert.Equal(t, len(original.SelectFields), len(copied.SelectFields))
|
||||
assert.Equal(t, original.SelectFields[0].Name, copied.SelectFields[0].Name)
|
||||
|
||||
require.NotNil(t, copied.LimitBy)
|
||||
assert.Equal(t, original.LimitBy.Value, copied.LimitBy.Value)
|
||||
assert.Equal(t, len(original.LimitBy.Keys), len(copied.LimitBy.Keys))
|
||||
|
||||
require.NotNil(t, copied.Having)
|
||||
assert.Equal(t, original.Having.Expression, copied.Having.Expression)
|
||||
|
||||
require.NotNil(t, copied.SecondaryAggregations)
|
||||
assert.Equal(t, len(original.SecondaryAggregations), len(copied.SecondaryAggregations))
|
||||
assert.Equal(t, original.SecondaryAggregations[0].Limit, copied.SecondaryAggregations[0].Limit)
|
||||
|
||||
require.NotNil(t, copied.Functions)
|
||||
assert.Equal(t, len(original.Functions), len(copied.Functions))
|
||||
assert.Equal(t, original.Functions[0].Name, copied.Functions[0].Name)
|
||||
|
||||
// Verify independence - modify copied and ensure original is unchanged
|
||||
copied.Name = "B"
|
||||
assert.Equal(t, "A", original.Name)
|
||||
|
||||
copied.Aggregations[0].Expression = "sum()"
|
||||
assert.Equal(t, "count()", original.Aggregations[0].Expression)
|
||||
|
||||
copied.Filter.Expression = "modified"
|
||||
assert.Equal(t, "service.name = 'frontend'", original.Filter.Expression)
|
||||
|
||||
copied.GroupBy[0].Name = "modified"
|
||||
assert.Equal(t, "service.name", original.GroupBy[0].Name)
|
||||
|
||||
copied.Order[0].Key.Name = "modified"
|
||||
assert.Equal(t, "timestamp", original.Order[0].Key.Name)
|
||||
|
||||
copied.SelectFields[0].Name = "modified"
|
||||
assert.Equal(t, "trace_id", original.SelectFields[0].Name)
|
||||
|
||||
copied.LimitBy.Value = "999"
|
||||
assert.Equal(t, "10", original.LimitBy.Value)
|
||||
|
||||
copied.Having.Expression = "modified"
|
||||
assert.Equal(t, "count() > 100", original.Having.Expression)
|
||||
|
||||
copied.SecondaryAggregations[0].Limit = 999
|
||||
assert.Equal(t, 10, original.SecondaryAggregations[0].Limit)
|
||||
|
||||
copied.Functions[0].Name = FunctionNameAbsolute
|
||||
assert.Equal(t, FunctionNameTimeShift, original.Functions[0].Name)
|
||||
})
|
||||
|
||||
t.Run("copy with nil fields", func(t *testing.T) {
|
||||
original := QueryBuilderQuery[TraceAggregation]{
|
||||
Name: "A",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
}
|
||||
|
||||
copied := original.Copy()
|
||||
|
||||
assert.Equal(t, original.Name, copied.Name)
|
||||
assert.Equal(t, original.Signal, copied.Signal)
|
||||
assert.Nil(t, copied.Aggregations)
|
||||
assert.Nil(t, copied.Filter)
|
||||
assert.Nil(t, copied.GroupBy)
|
||||
assert.Nil(t, copied.Order)
|
||||
assert.Nil(t, copied.SelectFields)
|
||||
assert.Nil(t, copied.LimitBy)
|
||||
assert.Nil(t, copied.Having)
|
||||
assert.Nil(t, copied.SecondaryAggregations)
|
||||
assert.Nil(t, copied.Functions)
|
||||
})
|
||||
|
||||
t.Run("copy metric aggregation query", func(t *testing.T) {
|
||||
original := QueryBuilderQuery[MetricAggregation]{
|
||||
Name: "M",
|
||||
Signal: telemetrytypes.SignalMetrics,
|
||||
Aggregations: []MetricAggregation{
|
||||
{
|
||||
MetricName: "cpu_usage",
|
||||
SpaceAggregation: metrictypes.SpaceAggregationAvg,
|
||||
TimeAggregation: metrictypes.TimeAggregationAvg,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
copied := original.Copy()
|
||||
|
||||
assert.Equal(t, original.Name, copied.Name)
|
||||
assert.Equal(t, original.Signal, copied.Signal)
|
||||
require.NotNil(t, copied.Aggregations)
|
||||
assert.Equal(t, original.Aggregations[0].MetricName, copied.Aggregations[0].MetricName)
|
||||
assert.Equal(t, original.Aggregations[0].SpaceAggregation, copied.Aggregations[0].SpaceAggregation)
|
||||
|
||||
// Verify independence
|
||||
copied.Aggregations[0].MetricName = "modified"
|
||||
assert.Equal(t, "cpu_usage", original.Aggregations[0].MetricName)
|
||||
})
|
||||
|
||||
t.Run("copy log aggregation query", func(t *testing.T) {
|
||||
original := QueryBuilderQuery[LogAggregation]{
|
||||
Name: "L",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Aggregations: []LogAggregation{
|
||||
{
|
||||
Expression: "count()",
|
||||
Alias: "log_count",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
copied := original.Copy()
|
||||
|
||||
assert.Equal(t, original.Name, copied.Name)
|
||||
assert.Equal(t, original.Signal, copied.Signal)
|
||||
require.NotNil(t, copied.Aggregations)
|
||||
assert.Equal(t, original.Aggregations[0].Expression, copied.Aggregations[0].Expression)
|
||||
|
||||
// Verify independence
|
||||
copied.Aggregations[0].Expression = "sum()"
|
||||
assert.Equal(t, "count()", original.Aggregations[0].Expression)
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryBuilderQuery_Normalize(t *testing.T) {
|
||||
t.Run("normalize select fields", func(t *testing.T) {
|
||||
query := QueryBuilderQuery[TraceAggregation]{
|
||||
SelectFields: []telemetrytypes.TelemetryFieldKey{
|
||||
{
|
||||
Name: "service.name",
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
},
|
||||
{
|
||||
Name: "span.name",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
query.Normalize()
|
||||
|
||||
// Normalize only changes FieldContext, not the Name
|
||||
assert.Equal(t, "service.name", query.SelectFields[0].Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextResource, query.SelectFields[0].FieldContext)
|
||||
assert.Equal(t, "span.name", query.SelectFields[1].Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextSpan, query.SelectFields[1].FieldContext)
|
||||
})
|
||||
|
||||
t.Run("normalize group by fields", func(t *testing.T) {
|
||||
query := QueryBuilderQuery[TraceAggregation]{
|
||||
GroupBy: []GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "service.name",
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
query.Normalize()
|
||||
|
||||
assert.Equal(t, "service.name", query.GroupBy[0].Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextResource, query.GroupBy[0].FieldContext)
|
||||
})
|
||||
|
||||
t.Run("normalize order by fields", func(t *testing.T) {
|
||||
query := QueryBuilderQuery[TraceAggregation]{
|
||||
Order: []OrderBy{
|
||||
{
|
||||
Key: OrderByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "timestamp",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
Direction: OrderDirectionDesc,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
query.Normalize()
|
||||
|
||||
assert.Equal(t, "timestamp", query.Order[0].Key.Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextSpan, query.Order[0].Key.FieldContext)
|
||||
})
|
||||
|
||||
t.Run("normalize secondary aggregations", func(t *testing.T) {
|
||||
query := QueryBuilderQuery[TraceAggregation]{
|
||||
SecondaryAggregations: []SecondaryAggregation{
|
||||
{
|
||||
Order: []OrderBy{
|
||||
{
|
||||
Key: OrderByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "value",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
Direction: OrderDirectionAsc,
|
||||
},
|
||||
},
|
||||
GroupBy: []GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "region",
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
query.Normalize()
|
||||
|
||||
assert.Equal(t, "value", query.SecondaryAggregations[0].Order[0].Key.Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextSpan, query.SecondaryAggregations[0].Order[0].Key.FieldContext)
|
||||
assert.Equal(t, "region", query.SecondaryAggregations[0].GroupBy[0].Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextResource, query.SecondaryAggregations[0].GroupBy[0].FieldContext)
|
||||
})
|
||||
|
||||
t.Run("normalize with nil fields", func(t *testing.T) {
|
||||
query := QueryBuilderQuery[TraceAggregation]{
|
||||
Name: "A",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
query.Normalize()
|
||||
|
||||
assert.Equal(t, "A", query.Name)
|
||||
})
|
||||
|
||||
t.Run("normalize all fields together", func(t *testing.T) {
|
||||
query := QueryBuilderQuery[TraceAggregation]{
|
||||
SelectFields: []telemetrytypes.TelemetryFieldKey{
|
||||
{Name: "service.name", FieldContext: telemetrytypes.FieldContextResource},
|
||||
},
|
||||
GroupBy: []GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "host.name",
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
},
|
||||
},
|
||||
},
|
||||
Order: []OrderBy{
|
||||
{
|
||||
Key: OrderByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "duration",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SecondaryAggregations: []SecondaryAggregation{
|
||||
{
|
||||
Order: []OrderBy{
|
||||
{
|
||||
Key: OrderByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "count",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "status.code",
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
query.Normalize()
|
||||
|
||||
assert.Equal(t, "service.name", query.SelectFields[0].Name)
|
||||
assert.Equal(t, "host.name", query.GroupBy[0].Name)
|
||||
assert.Equal(t, "duration", query.Order[0].Key.Name)
|
||||
assert.Equal(t, "count", query.SecondaryAggregations[0].Order[0].Key.Name)
|
||||
assert.Equal(t, "status.code", query.SecondaryAggregations[0].GroupBy[0].Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryBuilderQuery_UnmarshalJSON(t *testing.T) {
|
||||
t.Run("valid trace query", func(t *testing.T) {
|
||||
jsonData := `{
|
||||
"name": "A",
|
||||
"signal": "traces",
|
||||
"stepInterval": 60,
|
||||
"aggregations": [{
|
||||
"expression": "count()",
|
||||
"alias": "trace_count"
|
||||
}],
|
||||
"filter": {
|
||||
"expression": "service.name = 'frontend'"
|
||||
},
|
||||
"groupBy": [{
|
||||
"name": "service.name",
|
||||
"fieldContext": "resource"
|
||||
}],
|
||||
"order": [{
|
||||
"key": {
|
||||
"name": "service.name",
|
||||
"fieldContext": "resource"
|
||||
},
|
||||
"direction": "desc"
|
||||
}],
|
||||
"limit": 100
|
||||
}`
|
||||
|
||||
var query QueryBuilderQuery[TraceAggregation]
|
||||
err := json.Unmarshal([]byte(jsonData), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "A", query.Name)
|
||||
assert.Equal(t, telemetrytypes.SignalTraces, query.Signal)
|
||||
assert.Equal(t, int64(60000), query.StepInterval.Milliseconds())
|
||||
assert.Equal(t, 1, len(query.Aggregations))
|
||||
assert.Equal(t, "count()", query.Aggregations[0].Expression)
|
||||
assert.Equal(t, "trace_count", query.Aggregations[0].Alias)
|
||||
require.NotNil(t, query.Filter)
|
||||
assert.Equal(t, "service.name = 'frontend'", query.Filter.Expression)
|
||||
assert.Equal(t, 1, len(query.GroupBy))
|
||||
assert.Equal(t, "service.name", query.GroupBy[0].Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextResource, query.GroupBy[0].FieldContext)
|
||||
assert.Equal(t, 1, len(query.Order))
|
||||
assert.Equal(t, "service.name", query.Order[0].Key.Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextResource, query.Order[0].Key.FieldContext)
|
||||
assert.Equal(t, OrderDirectionDesc, query.Order[0].Direction)
|
||||
assert.Equal(t, 100, query.Limit)
|
||||
})
|
||||
|
||||
t.Run("valid metric query", func(t *testing.T) {
|
||||
jsonData := `{
|
||||
"name": "M",
|
||||
"signal": "metrics",
|
||||
"stepInterval": "1m",
|
||||
"aggregations": [{
|
||||
"metricName": "cpu_usage",
|
||||
"spaceAggregation": "avg",
|
||||
"timeAggregation": "avg"
|
||||
}]
|
||||
}`
|
||||
|
||||
var query QueryBuilderQuery[MetricAggregation]
|
||||
err := json.Unmarshal([]byte(jsonData), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "M", query.Name)
|
||||
assert.Equal(t, telemetrytypes.SignalMetrics, query.Signal)
|
||||
assert.Equal(t, int64(60000), query.StepInterval.Milliseconds())
|
||||
assert.Equal(t, 1, len(query.Aggregations))
|
||||
assert.Equal(t, "cpu_usage", query.Aggregations[0].MetricName)
|
||||
})
|
||||
|
||||
t.Run("valid log query", func(t *testing.T) {
|
||||
jsonData := `{
|
||||
"name": "L",
|
||||
"signal": "logs",
|
||||
"aggregations": [{
|
||||
"expression": "count()",
|
||||
"alias": "log_count"
|
||||
}]
|
||||
}`
|
||||
|
||||
var query QueryBuilderQuery[LogAggregation]
|
||||
err := json.Unmarshal([]byte(jsonData), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "L", query.Name)
|
||||
assert.Equal(t, telemetrytypes.SignalLogs, query.Signal)
|
||||
assert.Equal(t, 1, len(query.Aggregations))
|
||||
assert.Equal(t, "count()", query.Aggregations[0].Expression)
|
||||
})
|
||||
|
||||
t.Run("unknown field error", func(t *testing.T) {
|
||||
jsonData := `{
|
||||
"name": "A",
|
||||
"signal": "traces",
|
||||
"unknownField": "value"
|
||||
}`
|
||||
|
||||
var query QueryBuilderQuery[TraceAggregation]
|
||||
err := json.Unmarshal([]byte(jsonData), &query)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown field")
|
||||
})
|
||||
|
||||
t.Run("query with all optional fields", func(t *testing.T) {
|
||||
// NOTE: This json payload is not realistic, just for testing all fields
|
||||
jsonData := `{
|
||||
"name": "A",
|
||||
"signal": "traces",
|
||||
"stepInterval": "5m",
|
||||
"source": "traces",
|
||||
"aggregations": [{
|
||||
"expression": "count()",
|
||||
"alias": "span.count"
|
||||
}],
|
||||
"disabled": true,
|
||||
"filter": {
|
||||
"expression": "service.name = 'api'"
|
||||
},
|
||||
"groupBy": [{
|
||||
"name": "service.name",
|
||||
"fieldContext": "resource"
|
||||
}],
|
||||
"order": [{
|
||||
"key": {
|
||||
"name": "timestamp",
|
||||
"fieldContext": "span"
|
||||
},
|
||||
"direction": "asc"
|
||||
}],
|
||||
"selectFields": [{
|
||||
"name": "trace_id",
|
||||
"fieldContext": "span"
|
||||
}],
|
||||
"limit": 50,
|
||||
"offset": 10,
|
||||
"cursor": "cursor123",
|
||||
"limitBy": {
|
||||
"value": "5",
|
||||
"keys": ["service.name"]
|
||||
},
|
||||
"having": {
|
||||
"expression": "count() > 10"
|
||||
},
|
||||
"secondaryAggregations": [{
|
||||
"limit": 20,
|
||||
"order": [{
|
||||
"key": {
|
||||
"name": "value",
|
||||
"fieldContext": "span"
|
||||
},
|
||||
"direction": "desc"
|
||||
}],
|
||||
"groupBy": [{
|
||||
"name": "region",
|
||||
"fieldContext": "resource"
|
||||
}]
|
||||
}],
|
||||
"functions": [{
|
||||
"name": "timeShift",
|
||||
"args": [{
|
||||
"name": "shift",
|
||||
"value": "1h"
|
||||
}]
|
||||
}],
|
||||
"legend": "{{service.name}}"
|
||||
}`
|
||||
|
||||
var query QueryBuilderQuery[TraceAggregation]
|
||||
err := json.Unmarshal([]byte(jsonData), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "A", query.Name)
|
||||
assert.Equal(t, telemetrytypes.SignalTraces, query.Signal)
|
||||
assert.Equal(t, int64(300000), query.StepInterval.Milliseconds())
|
||||
// Source is set in the JSON, so it should be "traces", not SourceUnspecified
|
||||
assert.Equal(t, "traces", query.Source.String.StringValue())
|
||||
assert.True(t, query.Disabled)
|
||||
assert.Equal(t, query.Aggregations[0].Expression, "count()")
|
||||
assert.Equal(t, query.Aggregations[0].Alias, "span.count")
|
||||
assert.NotNil(t, query.Filter)
|
||||
assert.NotNil(t, query.GroupBy)
|
||||
assert.NotNil(t, query.Order)
|
||||
assert.NotNil(t, query.SelectFields)
|
||||
assert.Equal(t, 50, query.Limit)
|
||||
assert.Equal(t, 10, query.Offset)
|
||||
assert.Equal(t, "cursor123", query.Cursor)
|
||||
assert.NotNil(t, query.LimitBy)
|
||||
assert.NotNil(t, query.Having)
|
||||
assert.NotNil(t, query.SecondaryAggregations)
|
||||
assert.NotNil(t, query.Functions)
|
||||
assert.Equal(t, "{{service.name}}", query.Legend)
|
||||
})
|
||||
|
||||
t.Run("normalization happens during unmarshaling", func(t *testing.T) {
|
||||
jsonData := `{
|
||||
"name": "A",
|
||||
"signal": "traces",
|
||||
"selectFields": [{
|
||||
"name": "resource.service.name"
|
||||
}],
|
||||
"groupBy": [{
|
||||
"name": "resource.host.name"
|
||||
}],
|
||||
"order": [{
|
||||
"key": {
|
||||
"name": "span.duration"
|
||||
},
|
||||
"direction": "desc"
|
||||
}]
|
||||
}`
|
||||
|
||||
var query QueryBuilderQuery[TraceAggregation]
|
||||
err := json.Unmarshal([]byte(jsonData), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
// FieldContext should be normalized, Name should remain as-is
|
||||
assert.Equal(t, "service.name", query.SelectFields[0].Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextResource, query.SelectFields[0].FieldContext)
|
||||
assert.Equal(t, "host.name", query.GroupBy[0].Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextResource, query.GroupBy[0].FieldContext)
|
||||
assert.Equal(t, "duration", query.Order[0].Key.Name)
|
||||
assert.Equal(t, telemetrytypes.FieldContextSpan, query.Order[0].Key.FieldContext)
|
||||
})
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package querybuildertypesv5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"maps"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/cachetypes"
|
||||
)
|
||||
|
||||
var _ cachetypes.Cacheable = (*CachedData)(nil)
|
||||
|
||||
type CachedBucket struct {
|
||||
StartMs uint64 `json:"startMs"`
|
||||
EndMs uint64 `json:"endMs"`
|
||||
Type RequestType `json:"type"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
Stats ExecStats `json:"stats"`
|
||||
}
|
||||
|
||||
func (c *CachedBucket) Clone() *CachedBucket {
|
||||
return &CachedBucket{
|
||||
StartMs: c.StartMs,
|
||||
EndMs: c.EndMs,
|
||||
Type: c.Type,
|
||||
Value: bytes.Clone(c.Value),
|
||||
Stats: ExecStats{
|
||||
RowsScanned: c.Stats.RowsScanned,
|
||||
BytesScanned: c.Stats.BytesScanned,
|
||||
DurationMS: c.Stats.DurationMS,
|
||||
StepIntervals: maps.Clone(c.Stats.StepIntervals),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CachedData represents the full cached data for a query
|
||||
type CachedData struct {
|
||||
Buckets []*CachedBucket `json:"buckets"`
|
||||
Warnings []string `json:"warnings"`
|
||||
}
|
||||
|
||||
func (c *CachedData) UnmarshalBinary(data []byte) error {
|
||||
return json.Unmarshal(data, c)
|
||||
}
|
||||
|
||||
func (c *CachedData) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
func (c *CachedData) Clone() cachetypes.Cacheable {
|
||||
clonedCachedData := new(CachedData)
|
||||
clonedCachedData.Buckets = make([]*CachedBucket, len(c.Buckets))
|
||||
for i := range c.Buckets {
|
||||
clonedCachedData.Buckets[i] = c.Buckets[i].Clone()
|
||||
}
|
||||
|
||||
clonedCachedData.Warnings = make([]string, len(c.Warnings))
|
||||
copy(clonedCachedData.Warnings, c.Warnings)
|
||||
|
||||
return clonedCachedData
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package querybuildertypesv5
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func createBuckets_TimeSeries(numBuckets int) []*CachedBucket {
|
||||
buckets := make([]*CachedBucket, numBuckets)
|
||||
|
||||
for i := 0; i < numBuckets; i++ {
|
||||
startMs := uint64(i * 10000)
|
||||
endMs := uint64((i + 1) * 10000)
|
||||
|
||||
timeSeriesData := &TimeSeriesData{
|
||||
QueryName: "A",
|
||||
Aggregations: []*AggregationBucket{
|
||||
{
|
||||
Index: 0,
|
||||
Series: []*TimeSeries{
|
||||
{
|
||||
Labels: []*Label{
|
||||
{Key: telemetrytypes.TelemetryFieldKey{Name: "service"}, Value: "test"},
|
||||
},
|
||||
Values: []*TimeSeriesValue{
|
||||
{Timestamp: 1672563720000, Value: 1, Partial: true}, // 12:02
|
||||
{Timestamp: 1672563900000, Value: 2}, // 12:05
|
||||
{Timestamp: 1672564200000, Value: 2.5}, // 12:10
|
||||
{Timestamp: 1672564500000, Value: 2.6}, // 12:15
|
||||
{Timestamp: 1672566600000, Value: 2.9}, // 12:50
|
||||
{Timestamp: 1672566900000, Value: 3}, // 12:55
|
||||
{Timestamp: 1672567080000, Value: 4, Partial: true}, // 12:58
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
value, err := json.Marshal(timeSeriesData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buckets[i] = &CachedBucket{
|
||||
StartMs: startMs,
|
||||
EndMs: endMs,
|
||||
Type: RequestTypeTimeSeries,
|
||||
Value: json.RawMessage(value),
|
||||
Stats: ExecStats{
|
||||
RowsScanned: uint64(i * 500),
|
||||
BytesScanned: uint64(i * 10000),
|
||||
DurationMS: uint64(i * 1000),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return buckets
|
||||
}
|
||||
|
||||
func BenchmarkCachedData_JSONMarshal_10kbuckets(b *testing.B) {
|
||||
buckets := createBuckets_TimeSeries(10000)
|
||||
data := &CachedData{Buckets: buckets}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := json.Marshal(data)
|
||||
assert.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCachedData_Clone_10kbuckets(b *testing.B) {
|
||||
buckets := createBuckets_TimeSeries(10000)
|
||||
data := &CachedData{Buckets: buckets}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = data.Clone()
|
||||
}
|
||||
}
|
||||
@@ -73,43 +73,6 @@ func (f *QueryBuilderFormula) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if the QueryBuilderFormula fields are valid
|
||||
func (f QueryBuilderFormula) Validate() error {
|
||||
// Validate name is not blank
|
||||
if strings.TrimSpace(f.Name) == "" {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"formula name cannot be blank",
|
||||
)
|
||||
}
|
||||
|
||||
// Validate expression is not blank
|
||||
if strings.TrimSpace(f.Expression) == "" {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"formula expression cannot be blank",
|
||||
)
|
||||
}
|
||||
|
||||
// Validate functions if present
|
||||
for i, fn := range f.Functions {
|
||||
if err := fn.Validate(); err != nil {
|
||||
fnId := fmt.Sprintf("function #%d", i+1)
|
||||
if f.Name != "" {
|
||||
fnId = fmt.Sprintf("function #%d in formula '%s'", i+1, f.Name)
|
||||
}
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"invalid %s: %s",
|
||||
fnId,
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// small container to store the query name and index or alias reference
|
||||
// for a variable in the formula expression
|
||||
// read below for more details on aggregation references
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package querybuildertypesv5
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -36,46 +33,6 @@ var (
|
||||
FunctionNameFillZero = FunctionName{valuer.NewString("fillZero")}
|
||||
)
|
||||
|
||||
// Validate checks if the FunctionName is valid and one of the known types
|
||||
func (fn FunctionName) Validate() error {
|
||||
validFunctions := []FunctionName{
|
||||
FunctionNameCutOffMin,
|
||||
FunctionNameCutOffMax,
|
||||
FunctionNameClampMin,
|
||||
FunctionNameClampMax,
|
||||
FunctionNameAbsolute,
|
||||
FunctionNameRunningDiff,
|
||||
FunctionNameLog2,
|
||||
FunctionNameLog10,
|
||||
FunctionNameCumulativeSum,
|
||||
FunctionNameEWMA3,
|
||||
FunctionNameEWMA5,
|
||||
FunctionNameEWMA7,
|
||||
FunctionNameMedian3,
|
||||
FunctionNameMedian5,
|
||||
FunctionNameMedian7,
|
||||
FunctionNameTimeShift,
|
||||
FunctionNameAnomaly,
|
||||
FunctionNameFillZero,
|
||||
}
|
||||
|
||||
if slices.Contains(validFunctions, fn) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format valid functions as comma-separated string
|
||||
var validFunctionNames []string
|
||||
for _, fn := range validFunctions {
|
||||
validFunctionNames = append(validFunctionNames, fn.StringValue())
|
||||
}
|
||||
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"invalid function name: %s",
|
||||
fn.StringValue(),
|
||||
).WithAdditional(fmt.Sprintf("valid functions are: %s", strings.Join(validFunctionNames, ", ")))
|
||||
}
|
||||
|
||||
// ApplyFunction applies the given function to the result data
|
||||
func ApplyFunction(fn Function, result *TimeSeries) *TimeSeries {
|
||||
// Extract the function name and arguments
|
||||
@@ -155,61 +112,6 @@ func ApplyFunction(fn Function, result *TimeSeries) *TimeSeries {
|
||||
return result
|
||||
}
|
||||
|
||||
// ValidateArgs validates the arguments for the given function
|
||||
func (fn Function) ValidateArgs() error {
|
||||
// Extract the function name and arguments
|
||||
name := fn.Name
|
||||
args := fn.Args
|
||||
|
||||
switch name {
|
||||
case FunctionNameCutOffMin, FunctionNameCutOffMax, FunctionNameClampMin, FunctionNameClampMax:
|
||||
if len(args) == 0 {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"threshold value is required for function %s",
|
||||
name.StringValue(),
|
||||
)
|
||||
}
|
||||
_, err := parseFloat64Arg(args[0].Value)
|
||||
if err != nil {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"threshold value must be a floating value for function %s",
|
||||
name.StringValue(),
|
||||
)
|
||||
}
|
||||
case FunctionNameEWMA3, FunctionNameEWMA5, FunctionNameEWMA7:
|
||||
if len(args) == 0 {
|
||||
return nil // alpha is optional for EWMA functions
|
||||
}
|
||||
_, err := parseFloat64Arg(args[0].Value)
|
||||
if err != nil {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"alpha value must be a floating value for function %s",
|
||||
name.StringValue(),
|
||||
)
|
||||
}
|
||||
case FunctionNameTimeShift:
|
||||
if len(args) == 0 {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"time shift value is required for function %s",
|
||||
name.StringValue(),
|
||||
)
|
||||
}
|
||||
_, err := parseFloat64Arg(args[0].Value)
|
||||
if err != nil {
|
||||
return errors.NewInvalidInputf(
|
||||
errors.CodeInvalidInput,
|
||||
"time shift value must be a floating value for function %s",
|
||||
name.StringValue(),
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseFloat64Arg parses an argument to float64
|
||||
func parseFloat64Arg(value any) (float64, error) {
|
||||
switch v := value.(type) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user