mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-30 00:42:00 +08:00
Merge branch 'main' into stmt-builder-metrics
This commit is contained in:
commit
ded03c04d9
1
.gitignore
vendored
1
.gitignore
vendored
@ -66,6 +66,7 @@ e2e/.auth
|
||||
# go
|
||||
vendor/
|
||||
**/main/**
|
||||
__debug_bin**
|
||||
|
||||
# git-town
|
||||
.git-branches.toml
|
||||
|
@ -207,3 +207,11 @@ emailing:
|
||||
key_file_path:
|
||||
# The path to the certificate file.
|
||||
cert_file_path:
|
||||
|
||||
##################### Sharder (experimental) #####################
|
||||
sharder:
|
||||
# Specifies the sharder provider to use.
|
||||
provider: noop
|
||||
single:
|
||||
# The org id to which this instance belongs to.
|
||||
org_id: org_id
|
||||
|
@ -3,13 +3,15 @@ package httplicensing
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
@ -19,23 +21,31 @@ import (
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
store licensetypes.Store
|
||||
zeus zeus.Zeus
|
||||
config licensing.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
stopChan chan struct{}
|
||||
store licensetypes.Store
|
||||
zeus zeus.Zeus
|
||||
config licensing.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
orgGetter organization.Getter
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||
func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("http"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
|
||||
return New(ctx, providerSettings, config, store, zeus)
|
||||
return New(ctx, providerSettings, config, store, zeus, orgGetter)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus) (licensing.Licensing, error) {
|
||||
func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) (licensing.Licensing, error) {
|
||||
settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/ee/licensing/httplicensing")
|
||||
licensestore := sqllicensingstore.New(sqlstore)
|
||||
return &provider{store: licensestore, zeus: zeus, config: config, settings: settings, stopChan: make(chan struct{})}, nil
|
||||
return &provider{
|
||||
store: licensestore,
|
||||
zeus: zeus,
|
||||
config: config,
|
||||
settings: settings,
|
||||
orgGetter: orgGetter,
|
||||
stopChan: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Start(ctx context.Context) error {
|
||||
@ -67,13 +77,13 @@ func (provider *provider) Stop(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (provider *provider) Validate(ctx context.Context) error {
|
||||
organizations, err := provider.store.ListOrganizations(ctx)
|
||||
organizations, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, organizationID := range organizations {
|
||||
err := provider.Refresh(ctx, organizationID)
|
||||
for _, organization := range organizations {
|
||||
err := provider.Refresh(ctx, organization.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -168,6 +178,11 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.InitFeatures(ctx, activeLicense.Features)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@ -82,31 +81,6 @@ func (store *store) Update(ctx context.Context, organizationID valuer.UUID, stor
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) ListOrganizations(ctx context.Context) ([]valuer.UUID, error) {
|
||||
orgIDStrs := make([]string, 0)
|
||||
err := store.sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(new(types.Organization)).
|
||||
Column("id").
|
||||
Scan(ctx, &orgIDStrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgIDs := make([]valuer.UUID, len(orgIDStrs))
|
||||
for idx, orgIDStr := range orgIDStrs {
|
||||
orgID, err := valuer.NewUUID(orgIDStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orgIDs[idx] = orgID
|
||||
}
|
||||
|
||||
return orgIDs, nil
|
||||
|
||||
}
|
||||
|
||||
func (store *store) CreateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
|
@ -96,13 +96,7 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
// note: add ee override methods first
|
||||
|
||||
// routes available only in ee version
|
||||
|
||||
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/loginPrecheck", am.OpenAccess(ah.Signoz.Handlers.User.LoginPrecheck)).Methods(http.MethodGet)
|
||||
|
||||
// invite
|
||||
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.Signoz.Handlers.User.GetInvite)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/invite/accept", am.OpenAccess(ah.Signoz.Handlers.User.AcceptInvite)).Methods(http.MethodPost)
|
||||
|
||||
// paid plans specific routes
|
||||
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost)
|
||||
@ -114,9 +108,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.LicensingAPI.Portal)).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
|
||||
|
||||
// v3
|
||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Activate)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Refresh)).Methods(http.MethodPut)
|
||||
|
@ -1,62 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (ah *APIHandler) lockDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
ah.lockUnlockDashboard(w, r, true)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) unlockDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
ah.lockUnlockDashboard(w, r, false)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request, lock bool) {
|
||||
// Locking can only be done by the owner of the dashboard
|
||||
// or an admin
|
||||
|
||||
// - Fetch the dashboard
|
||||
// - Check if the user is the owner or an admin
|
||||
// - If yes, lock/unlock the dashboard
|
||||
// - If no, return 403
|
||||
|
||||
// Get the dashboard UUID from the request
|
||||
uuid := mux.Vars(r)["uuid"]
|
||||
if strings.HasPrefix(uuid, "integration") {
|
||||
render.Error(w, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "dashboards created by integrations cannot be modified"))
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := ah.Signoz.Modules.Dashboard.Get(r.Context(), claims.OrgID, uuid)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := claims.IsAdmin(); err != nil && (dashboard.CreatedBy != claims.Email) {
|
||||
render.Error(w, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "You are not authorized to lock/unlock this dashboard"))
|
||||
return
|
||||
}
|
||||
|
||||
// Lock/Unlock the dashboard
|
||||
err = ah.Signoz.Modules.Dashboard.LockUnlock(r.Context(), claims.OrgID, uuid, lock)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, "Dashboard updated successfully")
|
||||
}
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@ -113,6 +114,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
serverOptions.SigNoz.SQLStore,
|
||||
serverOptions.SigNoz.TelemetryStore,
|
||||
serverOptions.SigNoz.Prometheus,
|
||||
serverOptions.SigNoz.Modules.OrgGetter,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@ -157,7 +159,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
// start the usagemanager
|
||||
usageManager, err := usage.New(serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.Organization)
|
||||
usageManager, err := usage.New(serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.OrgGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -225,7 +227,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
&opAmpModel.AllAgents, agentConfMgr,
|
||||
)
|
||||
|
||||
orgs, err := apiHandler.Signoz.Modules.Organization.GetAll(context.Background())
|
||||
orgs, err := apiHandler.Signoz.Modules.OrgGetter.ListByOwnedKeyRange(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -240,11 +242,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server, error) {
|
||||
|
||||
r := baseapp.NewRouter()
|
||||
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}, s.serverOptions.SigNoz.Sharder, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.SigNoz.Sharder).Wrap)
|
||||
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
s.serverOptions.Config.APIServer.Timeout.Default,
|
||||
@ -275,8 +276,8 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
r := baseapp.NewRouter()
|
||||
am := middleware.NewAuthZ(s.serverOptions.SigNoz.Instrumentation.Logger())
|
||||
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}, s.serverOptions.SigNoz.Sharder, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.SigNoz.Sharder).Wrap)
|
||||
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
s.serverOptions.Config.APIServer.Timeout.Default,
|
||||
@ -297,6 +298,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
||||
apiHandler.RegisterThirdPartyApiRoutes(r, am)
|
||||
apiHandler.MetricExplorerRoutes(r, am)
|
||||
apiHandler.RegisterTraceFunnelsRoutes(r, am)
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
@ -450,6 +452,7 @@ func makeRulesManager(
|
||||
sqlstore sqlstore.SQLStore,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
prometheus prometheus.Prometheus,
|
||||
orgGetter organization.Getter,
|
||||
) (*baserules.Manager, error) {
|
||||
// create manager opts
|
||||
managerOpts := &baserules.ManagerOptions{
|
||||
@ -465,6 +468,7 @@ func makeRulesManager(
|
||||
PrepareTestRuleFunc: rules.TestNotification,
|
||||
Alertmanager: alertmanager,
|
||||
SQLStore: sqlstore,
|
||||
OrgGetter: orgGetter,
|
||||
}
|
||||
|
||||
// create Manager
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
pkglicensing "github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@ -133,8 +134,8 @@ func main() {
|
||||
zeus.Config(),
|
||||
httpzeus.NewProviderFactory(),
|
||||
licensing.Config(24*time.Hour, 3),
|
||||
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
|
||||
return httplicensing.NewProviderFactory(sqlstore, zeus)
|
||||
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
|
||||
return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter)
|
||||
},
|
||||
signoz.NewEmailingProviderFactories(),
|
||||
signoz.NewCacheProviderFactories(),
|
||||
|
@ -41,16 +41,16 @@ type Manager struct {
|
||||
|
||||
zeus zeus.Zeus
|
||||
|
||||
organizationModule organization.Module
|
||||
orgGetter organization.Getter
|
||||
}
|
||||
|
||||
func New(licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus, organizationModule organization.Module) (*Manager, error) {
|
||||
func New(licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus, orgGetter organization.Getter) (*Manager, error) {
|
||||
m := &Manager{
|
||||
clickhouseConn: clickhouseConn,
|
||||
licenseService: licenseService,
|
||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||
zeus: zeus,
|
||||
organizationModule: organizationModule,
|
||||
clickhouseConn: clickhouseConn,
|
||||
licenseService: licenseService,
|
||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||
zeus: zeus,
|
||||
orgGetter: orgGetter,
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@ -74,8 +74,7 @@ func (lm *Manager) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||
|
||||
organizations, err := lm.organizationModule.GetAll(context.Background())
|
||||
organizations, err := lm.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get organizations", zap.Error(err))
|
||||
return
|
||||
|
@ -28,6 +28,7 @@ import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
import { LicenseStatus } from 'types/api/licensesV3/getActive';
|
||||
import { Userpilot } from 'userpilot';
|
||||
import { extractDomain } from 'utils/app';
|
||||
|
||||
@ -171,11 +172,13 @@ function App(): JSX.Element {
|
||||
user &&
|
||||
!!user.email
|
||||
) {
|
||||
// either the active API returns error with 404 or 501 and if it returns a terminated license means it's on basic plan
|
||||
const isOnBasicPlan =
|
||||
activeLicenseFetchError &&
|
||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||
activeLicenseFetchError?.getHttpStatusCode(),
|
||||
);
|
||||
(activeLicenseFetchError &&
|
||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||
activeLicenseFetchError?.getHttpStatusCode(),
|
||||
)) ||
|
||||
(activeLicense?.status && activeLicense.status === LicenseStatus.INVALID);
|
||||
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||
|
||||
if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) {
|
||||
@ -190,6 +193,10 @@ function App(): JSX.Element {
|
||||
updatedRoutes = updatedRoutes.filter(
|
||||
(route) => route?.path !== ROUTES.BILLING,
|
||||
);
|
||||
|
||||
if (isEnterpriseSelfHostedUser) {
|
||||
updatedRoutes.push(LIST_LICENSES);
|
||||
}
|
||||
}
|
||||
// always add support route for cloud users
|
||||
updatedRoutes = [...updatedRoutes, SUPPORT_ROUTE];
|
||||
|
@ -1,27 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/create';
|
||||
|
||||
const createDashboard = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const url = props.uploadedGrafana ? '/dashboards/grafana' : '/dashboards';
|
||||
try {
|
||||
const response = await axios.post(url, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default createDashboard;
|
@ -1,9 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/delete';
|
||||
|
||||
const deleteDashboard = (props: Props): Promise<PayloadProps> =>
|
||||
axios
|
||||
.delete<PayloadProps>(`/dashboards/${props.uuid}`)
|
||||
.then((response) => response.data);
|
||||
|
||||
export default deleteDashboard;
|
@ -1,11 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { Props } from 'types/api/dashboard/get';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const getDashboard = (props: Props): Promise<Dashboard> =>
|
||||
axios
|
||||
.get<ApiResponse<Dashboard>>(`/dashboards/${props.uuid}`)
|
||||
.then((res) => res.data.data);
|
||||
|
||||
export default getDashboard;
|
@ -1,8 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
export const getAllDashboardList = (): Promise<Dashboard[]> =>
|
||||
axios
|
||||
.get<ApiResponse<Dashboard[]>>('/dashboards')
|
||||
.then((res) => res.data.data);
|
@ -1,11 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
interface LockDashboardProps {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
const lockDashboard = (props: LockDashboardProps): Promise<AxiosResponse> =>
|
||||
axios.put(`/dashboards/${props.uuid}/lock`);
|
||||
|
||||
export default lockDashboard;
|
@ -1,11 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
interface UnlockDashboardProps {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
const unlockDashboard = (props: UnlockDashboardProps): Promise<AxiosResponse> =>
|
||||
axios.put(`/dashboards/${props.uuid}/unlock`);
|
||||
|
||||
export default unlockDashboard;
|
@ -1,20 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/update';
|
||||
|
||||
const updateDashboard = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.put(`/dashboards/${props.uuid}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default updateDashboard;
|
23
frontend/src/api/v1/dashboards/create.ts
Normal file
23
frontend/src/api/v1/dashboards/create.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/create';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const create = async (props: Props): Promise<SuccessResponseV2<Dashboard>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/dashboards', {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
19
frontend/src/api/v1/dashboards/getAll.ts
Normal file
19
frontend/src/api/v1/dashboards/getAll.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Dashboard, PayloadProps } from 'types/api/dashboard/getAll';
|
||||
|
||||
const getAll = async (): Promise<SuccessResponseV2<Dashboard[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>('/dashboards');
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
21
frontend/src/api/v1/dashboards/id/delete.ts
Normal file
21
frontend/src/api/v1/dashboards/id/delete.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/delete';
|
||||
|
||||
const deleteDashboard = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.delete<PayloadProps>(`/dashboards/${props.id}`);
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteDashboard;
|
20
frontend/src/api/v1/dashboards/id/get.ts
Normal file
20
frontend/src/api/v1/dashboards/id/get.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/get';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const get = async (props: Props): Promise<SuccessResponseV2<Dashboard>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/dashboards/${props.id}`);
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default get;
|
23
frontend/src/api/v1/dashboards/id/lock.ts
Normal file
23
frontend/src/api/v1/dashboards/id/lock.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/lockUnlock';
|
||||
|
||||
const lock = async (props: Props): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(
|
||||
`/dashboards/${props.id}/lock`,
|
||||
{ lock: props.lock },
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default lock;
|
23
frontend/src/api/v1/dashboards/id/update.ts
Normal file
23
frontend/src/api/v1/dashboards/id/update.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/update';
|
||||
|
||||
const update = async (props: Props): Promise<SuccessResponseV2<Dashboard>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(`/dashboards/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default update;
|
@ -16,6 +16,7 @@ import JSONView from 'container/LogDetailedView/JsonView';
|
||||
import Overview from 'container/LogDetailedView/Overview';
|
||||
import {
|
||||
aggregateAttributesResourcesToString,
|
||||
escapeHtml,
|
||||
removeEscapeCharacters,
|
||||
unescapeString,
|
||||
} from 'container/LogDetailedView/utils';
|
||||
@ -118,7 +119,7 @@ function LogDetail({
|
||||
const htmlBody = useMemo(
|
||||
() => ({
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(log?.body || ''), {
|
||||
dompurify.sanitize(unescapeString(escapeHtml(log?.body || '')), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
),
|
||||
|
@ -7,7 +7,7 @@ import cx from 'classnames';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||
import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import dompurify from 'dompurify';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
@ -58,7 +58,7 @@ function LogGeneralField({
|
||||
const html = useMemo(
|
||||
() => ({
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(fieldValue), {
|
||||
dompurify.sanitize(unescapeString(escapeHtml(fieldValue)), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
),
|
||||
|
@ -5,7 +5,7 @@ import { DrawerProps } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||
import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
|
||||
import LogsExplorerContext from 'container/LogsExplorerContext';
|
||||
import dompurify from 'dompurify';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
@ -177,7 +177,7 @@ function RawLogView({
|
||||
const html = useMemo(
|
||||
() => ({
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(text), {
|
||||
dompurify.sanitize(unescapeString(escapeHtml(text)), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
),
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import createDashboard from 'api/v1/dashboards/create';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation } from 'react-query';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { ExportPanelProps } from '.';
|
||||
import {
|
||||
@ -33,26 +34,28 @@ function ExportPanelContainer({
|
||||
refetch,
|
||||
} = useGetAllDashboard();
|
||||
|
||||
const handleError = useAxiosError();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const {
|
||||
mutate: createNewDashboard,
|
||||
isLoading: createDashboardLoading,
|
||||
} = useMutation(createDashboard, {
|
||||
onSuccess: (data) => {
|
||||
if (data.payload) {
|
||||
onExport(data?.payload, true);
|
||||
if (data.data) {
|
||||
onExport(data?.data, true);
|
||||
}
|
||||
refetch();
|
||||
},
|
||||
onError: handleError,
|
||||
onError: (error) => {
|
||||
showErrorModal(error as APIError);
|
||||
},
|
||||
});
|
||||
|
||||
const options = useMemo(() => getSelectOptions(data || []), [data]);
|
||||
const options = useMemo(() => getSelectOptions(data?.data || []), [data]);
|
||||
|
||||
const handleExportClick = useCallback((): void => {
|
||||
const currentSelectedDashboard = data?.find(
|
||||
({ uuid }) => uuid === selectedDashboardId,
|
||||
const currentSelectedDashboard = data?.data?.find(
|
||||
({ id }) => id === selectedDashboardId,
|
||||
);
|
||||
|
||||
onExport(currentSelectedDashboard || null, false);
|
||||
@ -66,14 +69,18 @@ function ExportPanelContainer({
|
||||
);
|
||||
|
||||
const handleNewDashboard = useCallback(async () => {
|
||||
createNewDashboard({
|
||||
title: t('new_dashboard_title', {
|
||||
ns: 'dashboard',
|
||||
}),
|
||||
uploadedGrafana: false,
|
||||
version: ENTITY_VERSION_V4,
|
||||
});
|
||||
}, [t, createNewDashboard]);
|
||||
try {
|
||||
await createNewDashboard({
|
||||
title: t('new_dashboard_title', {
|
||||
ns: 'dashboard',
|
||||
}),
|
||||
uploadedGrafana: false,
|
||||
version: ENTITY_VERSION_V4,
|
||||
});
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
}, [createNewDashboard, t, showErrorModal]);
|
||||
|
||||
const isDashboardLoading = isAllDashboardsLoading || createDashboardLoading;
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { SelectProps } from 'antd';
|
||||
import { PayloadProps as AllDashboardsData } from 'types/api/dashboard/getAll';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
export const getSelectOptions = (
|
||||
data: AllDashboardsData,
|
||||
): SelectProps['options'] =>
|
||||
data.map(({ uuid, data }) => ({
|
||||
export const getSelectOptions = (data: Dashboard[]): SelectProps['options'] =>
|
||||
data.map(({ id, data }) => ({
|
||||
label: data.title,
|
||||
value: uuid,
|
||||
value: id,
|
||||
}));
|
||||
|
||||
export const filterOptions: SelectProps['filterOption'] = (
|
||||
|
@ -38,7 +38,7 @@ export default function DashboardEmptyState(): JSX.Element {
|
||||
setSelectedRowWidgetId(null);
|
||||
handleToggleDashboardSlider(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { AppProvider } from 'providers/App/App';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from 'store';
|
||||
@ -189,24 +190,26 @@ describe('WidgetGraphComponent', () => {
|
||||
it('should show correct menu items when hovering over more options while loading', async () => {
|
||||
const { getByTestId, findByRole, getByText, container } = render(
|
||||
<MockQueryClientProvider>
|
||||
<Provider store={store}>
|
||||
<AppProvider>
|
||||
<WidgetGraphComponent
|
||||
widget={mockProps.widget}
|
||||
queryResponse={mockProps.queryResponse}
|
||||
errorMessage={mockProps.errorMessage}
|
||||
version={mockProps.version}
|
||||
headerMenuList={mockProps.headerMenuList}
|
||||
isWarning={mockProps.isWarning}
|
||||
isFetchingResponse={mockProps.isFetchingResponse}
|
||||
setRequestData={mockProps.setRequestData}
|
||||
onClickHandler={mockProps.onClickHandler}
|
||||
onDragSelect={mockProps.onDragSelect}
|
||||
openTracesButton={mockProps.openTracesButton}
|
||||
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
|
||||
/>
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
<ErrorModalProvider>
|
||||
<Provider store={store}>
|
||||
<AppProvider>
|
||||
<WidgetGraphComponent
|
||||
widget={mockProps.widget}
|
||||
queryResponse={mockProps.queryResponse}
|
||||
errorMessage={mockProps.errorMessage}
|
||||
version={mockProps.version}
|
||||
headerMenuList={mockProps.headerMenuList}
|
||||
isWarning={mockProps.isWarning}
|
||||
isFetchingResponse={mockProps.isFetchingResponse}
|
||||
setRequestData={mockProps.setRequestData}
|
||||
onClickHandler={mockProps.onClickHandler}
|
||||
onDragSelect={mockProps.onDragSelect}
|
||||
openTracesButton={mockProps.openTracesButton}
|
||||
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
|
||||
/>
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
</ErrorModalProvider>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
|
@ -4,7 +4,6 @@ import { Skeleton, Tooltip, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
||||
@ -31,7 +30,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@ -119,29 +118,23 @@ function WidgetGraphComponent({
|
||||
const updatedLayout =
|
||||
selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || [];
|
||||
|
||||
const updatedSelectedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedSelectedDashboard: Props = {
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
widgets: updatedWidgets,
|
||||
layout: updatedLayout,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
id: selectedDashboard.id,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
setDeleteModal(false);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -166,7 +159,8 @@ function WidgetGraphComponent({
|
||||
|
||||
updateDashboardMutation.mutateAsync(
|
||||
{
|
||||
...selectedDashboard,
|
||||
id: selectedDashboard.id,
|
||||
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
layout,
|
||||
@ -183,9 +177,9 @@ function WidgetGraphComponent({
|
||||
},
|
||||
{
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
notifications.success({
|
||||
message: 'Panel cloned successfully, redirecting to new copy.',
|
||||
|
@ -6,7 +6,6 @@ import { Button, Form, Input, Modal, Typography } from 'antd';
|
||||
import { useForm } from 'antd/es/form/Form';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { themeColors } from 'constants/theme';
|
||||
@ -14,7 +13,6 @@ import { DEFAULT_ROW_NAME } from 'container/NewDashboard/DashboardDescription/ut
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { defaultTo, isUndefined } from 'lodash-es';
|
||||
@ -36,7 +34,8 @@ import { ItemCallback, Layout } from 'react-grid-layout';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
|
||||
@ -107,7 +106,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
|
||||
const updateDashboardMutation = useUpdateDashboard();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
|
||||
@ -158,20 +156,20 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current && !isUndefined(data)) {
|
||||
logEvent('Dashboard Detail: Opened', {
|
||||
dashboardId: data.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: data.title,
|
||||
numberOfPanels: data.widgets?.length,
|
||||
numberOfVariables: Object.keys(data?.variables || {}).length || 0,
|
||||
});
|
||||
logEventCalledRef.current = true;
|
||||
}
|
||||
}, [data]);
|
||||
}, [data, selectedDashboard?.id]);
|
||||
|
||||
const onSaveHandler = (): void => {
|
||||
if (!selectedDashboard) return;
|
||||
|
||||
const updatedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
panelMap: { ...currentPanelMap },
|
||||
@ -186,24 +184,18 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
return widget;
|
||||
}),
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutate(updatedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
setSelectedRowWidgetId(null);
|
||||
if (updatedDashboard.payload) {
|
||||
if (updatedDashboard.payload.data.layout)
|
||||
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||
if (updatedDashboard.data) {
|
||||
if (updatedDashboard.data.data.layout)
|
||||
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -286,33 +278,25 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
|
||||
updatedWidgets?.push(currentWidget);
|
||||
|
||||
const updatedSelectedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedSelectedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
widgets: updatedWidgets,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
if (setPanelMap)
|
||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||
form.setFieldValue('title', '');
|
||||
setIsSettingsModalOpen(false);
|
||||
setCurrentSelectRowId(null);
|
||||
},
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -447,34 +431,26 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
const updatedPanelMap = { ...currentPanelMap };
|
||||
delete updatedPanelMap[currentSelectRowId];
|
||||
|
||||
const updatedSelectedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedSelectedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
widgets: updatedWidgets,
|
||||
layout: updatedLayout,
|
||||
panelMap: updatedPanelMap,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
if (setPanelMap)
|
||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||
setIsDeleteModalOpen(false);
|
||||
setCurrentSelectRowId(null);
|
||||
},
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
const isDashboardEmpty = useMemo(
|
||||
|
@ -33,7 +33,7 @@ export default function Dashboards({
|
||||
useEffect(() => {
|
||||
if (!dashboardsList) return;
|
||||
|
||||
const sortedDashboards = dashboardsList.sort((a, b) => {
|
||||
const sortedDashboards = dashboardsList.data.sort((a, b) => {
|
||||
const aUpdateAt = new Date(a.updatedAt).getTime();
|
||||
const bUpdateAt = new Date(b.updatedAt).getTime();
|
||||
return bUpdateAt - aUpdateAt;
|
||||
@ -103,7 +103,7 @@ export default function Dashboards({
|
||||
<div className="home-dashboards-list-container home-data-item-container">
|
||||
<div className="dashboards-list">
|
||||
{sortedDashboards.slice(0, 5).map((dashboard) => {
|
||||
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.uuid}`;
|
||||
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.id}`;
|
||||
|
||||
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
|
||||
event.stopPropagation();
|
||||
@ -134,7 +134,7 @@ export default function Dashboards({
|
||||
<div className="dashboard-item-name-container home-data-item-name-container">
|
||||
<img
|
||||
src={
|
||||
dashboard.id % 2 === 0
|
||||
Math.random() % 2 === 0
|
||||
? '/Icons/eight-ball.svg'
|
||||
: '/Icons/circus-tent.svg'
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
} from 'antd';
|
||||
import { TableProps } from 'antd/lib';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import createDashboard from 'api/v1/dashboards/create';
|
||||
import { AxiosError } from 'axios';
|
||||
import cx from 'classnames';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
@ -63,6 +63,7 @@ import {
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import {
|
||||
ChangeEvent,
|
||||
@ -83,6 +84,7 @@ import {
|
||||
WidgetRow,
|
||||
Widgets,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
|
||||
import ImportJSON from './ImportJSON';
|
||||
@ -226,7 +228,7 @@ function DashboardsList(): JSX.Element {
|
||||
useEffect(() => {
|
||||
const filteredDashboards = filterDashboard(
|
||||
searchString,
|
||||
dashboardListResponse || [],
|
||||
dashboardListResponse?.data || [],
|
||||
);
|
||||
if (sortOrder.columnKey === 'updatedAt') {
|
||||
sortDashboardsByUpdatedAt(filteredDashboards || []);
|
||||
@ -256,17 +258,19 @@ function DashboardsList(): JSX.Element {
|
||||
errorMessage: '',
|
||||
});
|
||||
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const data: Data[] =
|
||||
dashboards?.map((e) => ({
|
||||
createdAt: e.createdAt,
|
||||
description: e.data.description || '',
|
||||
id: e.uuid,
|
||||
id: e.id,
|
||||
lastUpdatedTime: e.updatedAt,
|
||||
name: e.data.title,
|
||||
tags: e.data.tags || [],
|
||||
key: e.uuid,
|
||||
key: e.id,
|
||||
createdBy: e.createdBy,
|
||||
isLocked: !!e.isLocked || false,
|
||||
isLocked: !!e.locked || false,
|
||||
lastUpdatedBy: e.updatedBy,
|
||||
image: e.data.image || Base64Icons[0],
|
||||
variables: e.data.variables,
|
||||
@ -292,28 +296,20 @@ function DashboardsList(): JSX.Element {
|
||||
version: ENTITY_VERSION_V4,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
safeNavigate(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: response.payload.uuid,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
setNewDashboardState({
|
||||
...newDashboardState,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: response.error || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
safeNavigate(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: response.data.id,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
setNewDashboardState({
|
||||
...newDashboardState,
|
||||
error: true,
|
||||
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
|
||||
});
|
||||
}
|
||||
}, [newDashboardState, safeNavigate, t]);
|
||||
}, [newDashboardState, safeNavigate, showErrorModal, t]);
|
||||
|
||||
const onModalHandler = (uploadedGrafana: boolean): void => {
|
||||
logEvent('Dashboard List: Import JSON clicked', {});
|
||||
@ -327,7 +323,7 @@ function DashboardsList(): JSX.Element {
|
||||
const searchText = (event as React.BaseSyntheticEvent)?.target?.value || '';
|
||||
const filteredDashboards = filterDashboard(
|
||||
searchText,
|
||||
dashboardListResponse || [],
|
||||
dashboardListResponse?.data || [],
|
||||
);
|
||||
setDashboards(filteredDashboards);
|
||||
setIsFilteringDashboards(false);
|
||||
@ -677,7 +673,7 @@ function DashboardsList(): JSX.Element {
|
||||
!isUndefined(dashboardListResponse)
|
||||
) {
|
||||
logEvent('Dashboard List: Page visited', {
|
||||
number: dashboardListResponse?.length,
|
||||
number: dashboardListResponse?.data?.length,
|
||||
});
|
||||
logEventCalledRef.current = true;
|
||||
}
|
||||
|
@ -14,19 +14,21 @@ import {
|
||||
UploadProps,
|
||||
} from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import createDashboard from 'api/v1/dashboards/create';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
||||
import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
// #TODO: Lucide will be removing brand icons like GitHub in the future. In that case, we can use Simple Icons. https://simpleicons.org/
|
||||
// See more: https://github.com/lucide-icons/lucide/issues/94
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { DashboardData } from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
function ImportJSON({
|
||||
isImportJSONModalVisible,
|
||||
@ -74,6 +76,8 @@ function ImportJSON({
|
||||
}
|
||||
};
|
||||
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const onClickLoadJsonHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setDashboardCreating(true);
|
||||
@ -81,11 +85,6 @@ function ImportJSON({
|
||||
|
||||
const dashboardData = JSON.parse(editorValue) as DashboardData;
|
||||
|
||||
// Remove uuid from the dashboard data, in all cases - empty, duplicate or any valid not duplicate uuid
|
||||
if (dashboardData.uuid !== undefined) {
|
||||
delete dashboardData.uuid;
|
||||
}
|
||||
|
||||
if (dashboardData?.layout) {
|
||||
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
||||
} else {
|
||||
@ -97,28 +96,19 @@ function ImportJSON({
|
||||
uploadedGrafana,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
safeNavigate(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: response.payload.uuid,
|
||||
}),
|
||||
);
|
||||
logEvent('Dashboard List: New dashboard imported successfully', {
|
||||
dashboardId: response.payload?.uuid,
|
||||
dashboardName: response.payload?.data?.title,
|
||||
});
|
||||
} else {
|
||||
setIsCreateDashboardError(true);
|
||||
notifications.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
safeNavigate(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: response.data.id,
|
||||
}),
|
||||
);
|
||||
logEvent('Dashboard List: New dashboard imported successfully', {
|
||||
dashboardId: response.data?.id,
|
||||
dashboardName: response.data?.data?.title,
|
||||
});
|
||||
|
||||
setDashboardCreating(false);
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
setDashboardCreating(false);
|
||||
setIsCreateDashboardError(true);
|
||||
notifications.error({
|
||||
|
@ -6,8 +6,7 @@ import { executeSearchQueries } from '../utils';
|
||||
|
||||
describe('executeSearchQueries', () => {
|
||||
const firstDashboard: Dashboard = {
|
||||
id: 11111,
|
||||
uuid: uuid(),
|
||||
id: uuid(),
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
createdBy: '',
|
||||
@ -18,8 +17,7 @@ describe('executeSearchQueries', () => {
|
||||
},
|
||||
};
|
||||
const secondDashboard: Dashboard = {
|
||||
id: 22222,
|
||||
uuid: uuid(),
|
||||
id: uuid(),
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
createdBy: '',
|
||||
@ -30,8 +28,7 @@ describe('executeSearchQueries', () => {
|
||||
},
|
||||
};
|
||||
const thirdDashboard: Dashboard = {
|
||||
id: 333333,
|
||||
uuid: uuid(),
|
||||
id: uuid(),
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
createdBy: '',
|
||||
|
@ -59,7 +59,7 @@ export function DeleteButton({
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
deleteDashboardMutation.mutateAsync(undefined, {
|
||||
deleteDashboardMutation.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: t('dashboard:delete_dashboard_success', {
|
||||
|
@ -14,7 +14,7 @@ export const generateSearchData = (
|
||||
|
||||
dashboards.forEach((dashboard) => {
|
||||
dashboardSearchData.push({
|
||||
id: dashboard.uuid,
|
||||
id: dashboard.id,
|
||||
title: dashboard.data.title,
|
||||
description: dashboard.data.description,
|
||||
tags: dashboard.data.tags || [],
|
||||
|
@ -21,8 +21,10 @@ import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||
|
||||
import { DataType } from '../TableView';
|
||||
import {
|
||||
escapeHtml,
|
||||
filterKeyForField,
|
||||
jsonToDataNodes,
|
||||
parseFieldValue,
|
||||
recursiveParseJSON,
|
||||
removeEscapeCharacters,
|
||||
unescapeString,
|
||||
@ -85,7 +87,7 @@ export function TableViewActions(
|
||||
record.field === 'body'
|
||||
? {
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(record.value), {
|
||||
dompurify.sanitize(unescapeString(escapeHtml(record.value)), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
),
|
||||
@ -155,7 +157,11 @@ export function TableViewActions(
|
||||
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
||||
)
|
||||
}
|
||||
onClick={onClickHandler(OPERATORS['='], fieldFilterKey, fieldData.value)}
|
||||
onClick={onClickHandler(
|
||||
OPERATORS['='],
|
||||
fieldFilterKey,
|
||||
parseFieldValue(fieldData.value),
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Filter out value">
|
||||
@ -171,7 +177,7 @@ export function TableViewActions(
|
||||
onClick={onClickHandler(
|
||||
OPERATORS['!='],
|
||||
fieldFilterKey,
|
||||
fieldData.value,
|
||||
parseFieldValue(fieldData.value),
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
@ -259,6 +259,24 @@ export const getDataTypes = (value: unknown): DataTypes => {
|
||||
return determineType(value);
|
||||
};
|
||||
|
||||
// prevent html rendering in the value
|
||||
export const escapeHtml = (unsafe: string): string =>
|
||||
unsafe
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// parse field value to remove escaping characters
|
||||
export const parseFieldValue = (value: string): string => {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
// now we do not want to render colors everywhere like in tooltip and monaco editor hence we remove such codes to make
|
||||
// the log line readable
|
||||
export const removeEscapeCharacters = (str: string): string =>
|
||||
|
@ -28,16 +28,12 @@ import LogsExplorerTable from 'container/LogsExplorerTable';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import dayjs from 'dayjs';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import useClickOutside from 'hooks/useClickOutside';
|
||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
@ -98,7 +94,6 @@ function LogsExplorerViews({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
chartQueryKeyRef: MutableRefObject<any>;
|
||||
}): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
|
||||
// this is to respect the panel type present in the URL rather than defaulting it to list always.
|
||||
@ -141,8 +136,6 @@ function LogsExplorerViews({
|
||||
const [queryId, setQueryId] = useState<string>(v4());
|
||||
const [queryStats, setQueryStats] = useState<WsDataEvent>();
|
||||
|
||||
const handleAxisError = useAxiosError();
|
||||
|
||||
const listQuery = useMemo(() => {
|
||||
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
|
||||
|
||||
@ -396,11 +389,6 @@ function LogsExplorerViews({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data?.payload]);
|
||||
|
||||
const {
|
||||
mutate: updateDashboard,
|
||||
isLoading: isUpdateDashboardLoading,
|
||||
} = useUpdateDashboard();
|
||||
|
||||
const getUpdatedQueryForExport = useCallback((): Query => {
|
||||
const updatedQuery = cloneDeep(currentQuery);
|
||||
|
||||
@ -424,68 +412,22 @@ function LogsExplorerViews({
|
||||
? getUpdatedQueryForExport()
|
||||
: exportDefaultQuery;
|
||||
|
||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
||||
dashboard,
|
||||
query,
|
||||
widgetId,
|
||||
panelTypeParam,
|
||||
options.selectColumns,
|
||||
);
|
||||
|
||||
logEvent('Logs Explorer: Add to dashboard successful', {
|
||||
panelType,
|
||||
isNewDashboard,
|
||||
dashboardName: dashboard?.data?.title,
|
||||
});
|
||||
|
||||
updateDashboard(updatedDashboard, {
|
||||
onSuccess: (data) => {
|
||||
if (data.error) {
|
||||
const message =
|
||||
data.error === 'feature usage exceeded' ? (
|
||||
<span>
|
||||
Panel limit exceeded for {DataSource.LOGS} in community edition. Please
|
||||
checkout our paid plans{' '}
|
||||
<a
|
||||
href="https://signoz.io/pricing/?utm_source=product&utm_medium=dashboard-limit"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</span>
|
||||
) : (
|
||||
data.error
|
||||
);
|
||||
notifications.error({
|
||||
message,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query,
|
||||
panelType: panelTypeParam,
|
||||
dashboardId: data.payload?.uuid || '',
|
||||
widgetId,
|
||||
});
|
||||
|
||||
safeNavigate(dashboardEditView);
|
||||
},
|
||||
onError: handleAxisError,
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query,
|
||||
panelType: panelTypeParam,
|
||||
dashboardId: dashboard.id,
|
||||
widgetId,
|
||||
});
|
||||
|
||||
safeNavigate(dashboardEditView);
|
||||
},
|
||||
[
|
||||
getUpdatedQueryForExport,
|
||||
exportDefaultQuery,
|
||||
options.selectColumns,
|
||||
safeNavigate,
|
||||
notifications,
|
||||
panelType,
|
||||
updateDashboard,
|
||||
handleAxisError,
|
||||
],
|
||||
[getUpdatedQueryForExport, exportDefaultQuery, safeNavigate, panelType],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -811,7 +753,6 @@ function LogsExplorerViews({
|
||||
<ExplorerOptionWrapper
|
||||
disabled={!stagedQuery}
|
||||
query={exportDefaultQuery}
|
||||
isLoading={isUpdateDashboardLoading}
|
||||
onExport={handleExport}
|
||||
sourcepage={DataSource.LOGS}
|
||||
/>
|
||||
|
@ -2,18 +2,12 @@ import './Explorer.styles.scss';
|
||||
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Switch } from 'antd';
|
||||
import axios from 'axios';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
|
||||
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
@ -39,13 +33,6 @@ function Explorer(): JSX.Element {
|
||||
currentQuery,
|
||||
} = useQueryBuilder();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const { notifications } = useNotifications();
|
||||
const { mutate: updateDashboard, isLoading } = useUpdateDashboard();
|
||||
const { options } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.METRICS_LIST_OPTIONS,
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateOperator: 'noop',
|
||||
});
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const isOneChartPerQueryEnabled =
|
||||
@ -86,59 +73,16 @@ function Explorer(): JSX.Element {
|
||||
|
||||
const widgetId = uuid();
|
||||
|
||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
||||
dashboard,
|
||||
queryToExport || exportDefaultQuery,
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query: queryToExport || exportDefaultQuery,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
dashboardId: dashboard.id,
|
||||
widgetId,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
options.selectColumns,
|
||||
);
|
||||
|
||||
updateDashboard(updatedDashboard, {
|
||||
onSuccess: (data) => {
|
||||
if (data.error) {
|
||||
const message =
|
||||
data.error === 'feature usage exceeded' ? (
|
||||
<span>
|
||||
Panel limit exceeded for {DataSource.METRICS} in community edition.
|
||||
Please checkout our paid plans{' '}
|
||||
<a
|
||||
href="https://signoz.io/pricing/?utm_source=product&utm_medium=dashboard-limit"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</span>
|
||||
) : (
|
||||
data.error
|
||||
);
|
||||
notifications.error({
|
||||
message,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query: queryToExport || exportDefaultQuery,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
dashboardId: data.payload?.uuid || '',
|
||||
widgetId,
|
||||
});
|
||||
|
||||
safeNavigate(dashboardEditView);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
notifications.error({
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
safeNavigate(dashboardEditView);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[exportDefaultQuery, notifications, updateDashboard],
|
||||
[exportDefaultQuery, safeNavigate],
|
||||
);
|
||||
|
||||
const splitedQueries = useMemo(
|
||||
@ -201,7 +145,6 @@ function Explorer(): JSX.Element {
|
||||
<ExplorerOptionWrapper
|
||||
disabled={!stagedQuery}
|
||||
query={exportDefaultQuery}
|
||||
isLoading={isLoading}
|
||||
sourcepage={DataSource.METRICS}
|
||||
onExport={handleExport}
|
||||
isOneChartPerQuery={showOneChartPerQuery}
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -44,11 +43,8 @@ import { FullScreenHandle } from 'react-full-screen';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import {
|
||||
Dashboard,
|
||||
DashboardData,
|
||||
IDashboardVariable,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import { DashboardData, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@ -65,10 +61,9 @@ interface DashboardDescriptionProps {
|
||||
|
||||
export function sanitizeDashboardData(
|
||||
selectedData: DashboardData,
|
||||
): Omit<DashboardData, 'uuid'> {
|
||||
): DashboardData {
|
||||
if (!selectedData?.variables) {
|
||||
const { uuid, ...rest } = selectedData;
|
||||
return rest;
|
||||
return selectedData;
|
||||
}
|
||||
|
||||
const updatedVariables = Object.entries(selectedData.variables).reduce(
|
||||
@ -80,9 +75,8 @@ export function sanitizeDashboardData(
|
||||
{} as Record<string, IDashboardVariable>,
|
||||
);
|
||||
|
||||
const { uuid, ...restData } = selectedData;
|
||||
return {
|
||||
...restData,
|
||||
...selectedData,
|
||||
variables: updatedVariables,
|
||||
};
|
||||
}
|
||||
@ -108,7 +102,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const selectedData = selectedDashboard
|
||||
? {
|
||||
...selectedDashboard.data,
|
||||
uuid: selectedDashboard.uuid,
|
||||
uuid: selectedDashboard.id,
|
||||
}
|
||||
: ({} as DashboardData);
|
||||
|
||||
@ -162,7 +156,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
setSelectedRowWidgetId(null);
|
||||
handleToggleDashboardSlider(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
||||
});
|
||||
@ -178,8 +172,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
if (!selectedDashboard) {
|
||||
return;
|
||||
}
|
||||
const updatedDashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
title: updatedTitle,
|
||||
@ -191,13 +186,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
message: 'Dashboard renamed successfully',
|
||||
});
|
||||
setIsRenameDashboardOpen(false);
|
||||
if (updatedDashboard.payload)
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (updatedDashboard.data) setSelectedDashboard(updatedDashboard.data);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
setIsRenameDashboardOpen(true);
|
||||
},
|
||||
});
|
||||
@ -251,8 +242,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
}
|
||||
}
|
||||
|
||||
const updatedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
layout: [
|
||||
@ -279,28 +271,21 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
},
|
||||
],
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutate(updatedDashboard, {
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (updatedDashboard.payload) {
|
||||
if (updatedDashboard.payload.data.layout)
|
||||
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||
if (updatedDashboard.data) {
|
||||
if (updatedDashboard.data.data.layout)
|
||||
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||
}
|
||||
|
||||
setIsPanelNameModalOpen(false);
|
||||
setSectionName(DEFAULT_ROW_NAME);
|
||||
},
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -445,7 +430,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
<DeleteButton
|
||||
createdBy={selectedDashboard?.createdBy || ''}
|
||||
name={selectedDashboard?.data.title || ''}
|
||||
id={String(selectedDashboard?.uuid) || ''}
|
||||
id={String(selectedDashboard?.id) || ''}
|
||||
isLocked={isDashboardLocked}
|
||||
routeToListPage
|
||||
/>
|
||||
|
@ -1,10 +1,8 @@
|
||||
import './GeneralSettings.styles.scss';
|
||||
|
||||
import { Col, Input, Select, Space, Typography } from 'antd';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import AddTags from 'container/NewDashboard/DashboardSettings/General/AddTags';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
@ -38,14 +36,12 @@ function GeneralDashboardSettings(): JSX.Element {
|
||||
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onSaveHandler = (): void => {
|
||||
if (!selectedDashboard) return;
|
||||
|
||||
updateDashboardMutation.mutateAsync(
|
||||
updateDashboardMutation.mutate(
|
||||
{
|
||||
...selectedDashboard,
|
||||
id: selectedDashboard.id,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
description: updatedDescription,
|
||||
@ -56,15 +52,11 @@ function GeneralDashboardSettings(): JSX.Element {
|
||||
},
|
||||
{
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
onError: () => {},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
@ -171,7 +171,8 @@ function VariablesSetting({
|
||||
|
||||
updateMutation.mutateAsync(
|
||||
{
|
||||
...selectedDashboard,
|
||||
id: selectedDashboard.id,
|
||||
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
variables: updatedVariablesData,
|
||||
@ -179,18 +180,13 @@ function VariablesSetting({
|
||||
},
|
||||
{
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
notifications.success({
|
||||
message: t('variable_updated_successfully'),
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: t('error_while_updating_variable'),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
@ -127,7 +127,7 @@ function QuerySection({
|
||||
panelType: selectedWidget.panelTypes,
|
||||
queryType: currentQuery.queryType,
|
||||
widgetId: selectedWidget.id,
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
isNewPanel,
|
||||
});
|
||||
|
@ -17,7 +17,6 @@ import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
@ -41,10 +40,10 @@ import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import {
|
||||
ColumnUnit,
|
||||
Dashboard,
|
||||
LegendPosition,
|
||||
Widgets,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
@ -141,7 +140,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
if (!logEventCalledRef.current) {
|
||||
logEvent('Panel Edit: Page visited', {
|
||||
panelType: selectedWidget?.panelTypes,
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
widgetId: selectedWidget?.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
isNewPanel: !!isWidgetNotPresent,
|
||||
@ -345,8 +344,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
return { selectedWidget, preWidgets, afterWidgets };
|
||||
}, [selectedDashboard, query]);
|
||||
|
||||
const handleError = useAxiosError();
|
||||
|
||||
// this loading state is to take care of mismatch in the responses for table and other panels
|
||||
// hence while changing the query contains the older value and the processing logic fails
|
||||
const [isLoadingPanelData, setIsLoadingPanelData] = useState<boolean>(false);
|
||||
@ -470,9 +467,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
updatedLayout = newLayoutItem;
|
||||
}
|
||||
|
||||
const dashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
uuid: selectedDashboard.uuid,
|
||||
const dashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
widgets: isNewDashboard
|
||||
@ -540,15 +537,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutateAsync(dashboard, {
|
||||
onSuccess: () => {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
setSelectedRowWidgetId(null);
|
||||
setSelectedDashboard(dashboard);
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
setToScrollWidgetId(selectedWidget?.id || '');
|
||||
safeNavigate({
|
||||
pathname: generatePath(ROUTES.DASHBOARD, { dashboardId }),
|
||||
});
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
}, [
|
||||
selectedDashboard,
|
||||
@ -562,7 +558,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
currentQuery,
|
||||
preWidgets,
|
||||
updateDashboardMutation,
|
||||
handleError,
|
||||
widgets,
|
||||
setSelectedDashboard,
|
||||
setToScrollWidgetId,
|
||||
@ -601,7 +596,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
|
||||
logEvent('Panel Edit: Save changes', {
|
||||
panelType: selectedWidget.panelTypes,
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
widgetId: selectedWidget.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
queryType: currentQuery.queryType,
|
||||
|
@ -26,6 +26,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { LicenseStatus } from 'types/api/licensesV3/getActive';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { checkVersionState } from 'utils/app';
|
||||
@ -301,10 +302,11 @@ function SideNav(): JSX.Element {
|
||||
}
|
||||
|
||||
const isOnBasicPlan =
|
||||
activeLicenseFetchError &&
|
||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||
activeLicenseFetchError?.getHttpStatusCode(),
|
||||
);
|
||||
(activeLicenseFetchError &&
|
||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||
activeLicenseFetchError?.getHttpStatusCode(),
|
||||
)) ||
|
||||
(activeLicense?.status && activeLicense.status === LicenseStatus.INVALID);
|
||||
|
||||
if (user.role !== USER_ROLES.ADMIN || isOnBasicPlan) {
|
||||
updatedMenuItems = updatedMenuItems.filter(
|
||||
@ -353,6 +355,7 @@ function SideNav(): JSX.Element {
|
||||
t,
|
||||
user.role,
|
||||
activeLicenseFetchError,
|
||||
activeLicense?.status,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
@ -1,15 +1,23 @@
|
||||
import deleteDashboard from 'api/dashboard/delete';
|
||||
import deleteDashboard from 'api/v1/dashboards/id/delete';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
import { PayloadProps } from 'types/api/dashboard/delete';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
export const useDeleteDashboard = (
|
||||
id: string,
|
||||
): UseMutationResult<PayloadProps, unknown, void, unknown> =>
|
||||
useMutation({
|
||||
): UseMutationResult<SuccessResponseV2<null>, APIError, void, unknown> => {
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
return useMutation<SuccessResponseV2<null>, APIError>({
|
||||
mutationKey: REACT_QUERY_KEY.DELETE_DASHBOARD,
|
||||
mutationFn: () =>
|
||||
deleteDashboard({
|
||||
uuid: id,
|
||||
id,
|
||||
}),
|
||||
onError: (error: APIError) => {
|
||||
showErrorModal(error);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -1,10 +1,22 @@
|
||||
import { getAllDashboardList } from 'api/dashboard/getAll';
|
||||
import getAll from 'api/v1/dashboards/getAll';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
export const useGetAllDashboard = (): UseQueryResult<Dashboard[], unknown> =>
|
||||
useQuery<Dashboard[]>({
|
||||
queryFn: getAllDashboardList,
|
||||
export const useGetAllDashboard = (): UseQueryResult<
|
||||
SuccessResponseV2<Dashboard[]>,
|
||||
APIError
|
||||
> => {
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
return useQuery<SuccessResponseV2<Dashboard[]>, APIError>({
|
||||
queryFn: getAll,
|
||||
onError: (error) => {
|
||||
showErrorModal(error);
|
||||
},
|
||||
queryKey: REACT_QUERY_KEY.GET_ALL_DASHBOARDS,
|
||||
});
|
||||
};
|
||||
|
@ -1,25 +1,31 @@
|
||||
import update from 'api/dashboard/update';
|
||||
import update from 'api/v1/dashboards/id/update';
|
||||
import dayjs from 'dayjs';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
export const useUpdateDashboard = (): UseUpdateDashboard => {
|
||||
const { updatedTimeRef } = useDashboard();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
return useMutation(update, {
|
||||
onSuccess: (data) => {
|
||||
if (data.payload) {
|
||||
updatedTimeRef.current = dayjs(data.payload.updatedAt);
|
||||
if (data.data) {
|
||||
updatedTimeRef.current = dayjs(data.data.updatedAt);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
showErrorModal(error);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
type UseUpdateDashboard = UseMutationResult<
|
||||
SuccessResponse<Dashboard> | ErrorResponse,
|
||||
unknown,
|
||||
SuccessResponseV2<Dashboard>,
|
||||
APIError,
|
||||
Props,
|
||||
unknown
|
||||
>;
|
||||
|
@ -38,7 +38,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
||||
logEvent('Panel Edit: Create alert', {
|
||||
panelType: widget.panelTypes,
|
||||
dashboardName: selectedDashboard?.data?.title,
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
widgetId: widget.id,
|
||||
queryType: widget.query.queryType,
|
||||
});
|
||||
@ -47,7 +47,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
||||
action: MenuItemKeys.CreateAlerts,
|
||||
panelType: widget.panelTypes,
|
||||
dashboardName: selectedDashboard?.data?.title,
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
widgetId: widget.id,
|
||||
queryType: widget.query.queryType,
|
||||
});
|
||||
|
@ -3,8 +3,7 @@ export const dashboardSuccessResponse = {
|
||||
status: 'success',
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
uuid: '1',
|
||||
id: '1',
|
||||
createdAt: '2022-11-16T13:29:47.064874419Z',
|
||||
createdBy: null,
|
||||
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
||||
@ -23,8 +22,7 @@ export const dashboardSuccessResponse = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
uuid: '2',
|
||||
id: '2',
|
||||
createdAt: '2022-11-16T13:20:47.064874419Z',
|
||||
createdBy: null,
|
||||
updatedAt: '2024-05-21T06:42:30.546630961Z',
|
||||
@ -53,8 +51,7 @@ export const dashboardEmptyState = {
|
||||
export const getDashboardById = {
|
||||
status: 'success',
|
||||
data: {
|
||||
id: 1,
|
||||
uuid: '1',
|
||||
id: '1',
|
||||
createdAt: '2022-11-16T13:29:47.064874419Z',
|
||||
createdBy: 'integration',
|
||||
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
||||
@ -78,8 +75,7 @@ export const getDashboardById = {
|
||||
export const getNonIntegrationDashboardById = {
|
||||
status: 'success',
|
||||
data: {
|
||||
id: 1,
|
||||
uuid: '1',
|
||||
id: '1',
|
||||
createdAt: '2022-11-16T13:29:47.064874419Z',
|
||||
createdBy: 'thor',
|
||||
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
||||
|
@ -234,7 +234,6 @@ describe('dashboard list page', () => {
|
||||
const firstDashboardData = dashboardSuccessResponse.data[0];
|
||||
expect(dashboardUtils.sanitizeDashboardData).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: firstDashboardData.uuid,
|
||||
title: firstDashboardData.data.title,
|
||||
createdAt: firstDashboardData.createdAt,
|
||||
}),
|
||||
|
@ -19,9 +19,9 @@ function DashboardPage(): JSX.Element {
|
||||
: 'Something went wrong';
|
||||
|
||||
useEffect(() => {
|
||||
const dashboardTitle = dashboardResponse.data?.data.title;
|
||||
const dashboardTitle = dashboardResponse.data?.data.data.title;
|
||||
document.title = dashboardTitle || document.title;
|
||||
}, [dashboardResponse.data?.data.title, isFetching]);
|
||||
}, [dashboardResponse.data?.data.data.title, isFetching]);
|
||||
|
||||
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {
|
||||
return <NotFound />;
|
||||
|
@ -4,7 +4,6 @@ import { FilterOutlined } from '@ant-design/icons';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Button, Card, Tabs, Tooltip } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import axios from 'axios';
|
||||
import cx from 'classnames';
|
||||
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
@ -19,13 +18,10 @@ import RightToolbarActions from 'container/QueryBuilder/components/ToolbarAction
|
||||
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { defaultSelectedColumns } from 'container/TracesExplorer/ListView/configs';
|
||||
import QuerySection from 'container/TracesExplorer/QuerySection';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { cloneDeep, isEmpty, set } from 'lodash-es';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
@ -40,8 +36,6 @@ import { ActionsWrapper, Container } from './styles';
|
||||
import { getTabsItems } from './utils';
|
||||
|
||||
function TracesExplorer(): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
@ -124,9 +118,7 @@ function TracesExplorer(): JSX.Element {
|
||||
[currentQuery, updateAllQueriesOperators],
|
||||
);
|
||||
|
||||
const { mutate: updateDashboard, isLoading } = useUpdateDashboard();
|
||||
|
||||
const getUpdatedQueryForExport = (): Query => {
|
||||
const getUpdatedQueryForExport = useCallback((): Query => {
|
||||
const updatedQuery = cloneDeep(currentQuery);
|
||||
|
||||
set(
|
||||
@ -136,7 +128,7 @@ function TracesExplorer(): JSX.Element {
|
||||
);
|
||||
|
||||
return updatedQuery;
|
||||
};
|
||||
}, [currentQuery, options.selectColumns]);
|
||||
|
||||
const handleExport = useCallback(
|
||||
(dashboard: Dashboard | null, isNewDashboard?: boolean): void => {
|
||||
@ -153,65 +145,22 @@ function TracesExplorer(): JSX.Element {
|
||||
? getUpdatedQueryForExport()
|
||||
: exportDefaultQuery;
|
||||
|
||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
||||
dashboard,
|
||||
query,
|
||||
widgetId,
|
||||
panelTypeParam,
|
||||
options.selectColumns,
|
||||
);
|
||||
|
||||
logEvent('Traces Explorer: Add to dashboard successful', {
|
||||
panelType,
|
||||
isNewDashboard,
|
||||
dashboardName: dashboard?.data?.title,
|
||||
});
|
||||
|
||||
updateDashboard(updatedDashboard, {
|
||||
onSuccess: (data) => {
|
||||
if (data.error) {
|
||||
const message =
|
||||
data.error === 'feature usage exceeded' ? (
|
||||
<span>
|
||||
Panel limit exceeded for {DataSource.TRACES} in community edition.
|
||||
Please checkout our paid plans{' '}
|
||||
<a
|
||||
href="https://signoz.io/pricing/?utm_source=product&utm_medium=dashboard-limit"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</span>
|
||||
) : (
|
||||
data.error
|
||||
);
|
||||
notifications.error({
|
||||
message,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query,
|
||||
panelType: panelTypeParam,
|
||||
dashboardId: data.payload?.uuid || '',
|
||||
widgetId,
|
||||
});
|
||||
|
||||
safeNavigate(dashboardEditView);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
notifications.error({
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query,
|
||||
panelType: panelTypeParam,
|
||||
dashboardId: dashboard.id,
|
||||
widgetId,
|
||||
});
|
||||
|
||||
safeNavigate(dashboardEditView);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[exportDefaultQuery, notifications, panelType, updateDashboard],
|
||||
[exportDefaultQuery, panelType, safeNavigate, getUpdatedQueryForExport],
|
||||
);
|
||||
|
||||
useShareBuilderUrl(defaultQuery);
|
||||
@ -282,11 +231,7 @@ function TracesExplorer(): JSX.Element {
|
||||
|
||||
<Container className="traces-explorer-views">
|
||||
<ActionsWrapper>
|
||||
<ExportPanel
|
||||
query={exportDefaultQuery}
|
||||
isLoading={isLoading}
|
||||
onExport={handleExport}
|
||||
/>
|
||||
<ExportPanel query={exportDefaultQuery} onExport={handleExport} />
|
||||
</ActionsWrapper>
|
||||
|
||||
<Tabs
|
||||
@ -299,7 +244,6 @@ function TracesExplorer(): JSX.Element {
|
||||
<ExplorerOptionWrapper
|
||||
disabled={!stagedQuery}
|
||||
query={exportDefaultQuery}
|
||||
isLoading={isLoading}
|
||||
sourcepage={DataSource.TRACES}
|
||||
onExport={handleExport}
|
||||
/>
|
||||
|
@ -1,14 +1,12 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Modal } from 'antd';
|
||||
import getDashboard from 'api/dashboard/get';
|
||||
import lockDashboardApi from 'api/dashboard/lockDashboard';
|
||||
import unlockDashboardApi from 'api/dashboard/unlockDashboard';
|
||||
import getDashboard from 'api/v1/dashboards/id/get';
|
||||
import locked from 'api/v1/dashboards/id/lock';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { useDashboardVariablesFromLocalStorage } from 'hooks/dashboard/useDashboardFromLocalStorage';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useTabVisibility from 'hooks/useTabFocus';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
@ -18,6 +16,7 @@ import isEqual from 'lodash-es/isEqual';
|
||||
import isUndefined from 'lodash-es/isUndefined';
|
||||
import omitBy from 'lodash-es/omitBy';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
@ -36,7 +35,9 @@ import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
@ -52,7 +53,10 @@ const DashboardContext = createContext<IDashboardContext>({
|
||||
isDashboardLocked: false,
|
||||
handleToggleDashboardSlider: () => {},
|
||||
handleDashboardLockToggle: () => {},
|
||||
dashboardResponse: {} as UseQueryResult<Dashboard, unknown>,
|
||||
dashboardResponse: {} as UseQueryResult<
|
||||
SuccessResponseV2<Dashboard>,
|
||||
APIError
|
||||
>,
|
||||
selectedDashboard: {} as Dashboard,
|
||||
dashboardId: '',
|
||||
layouts: [],
|
||||
@ -116,6 +120,8 @@ export function DashboardProvider({
|
||||
exact: true,
|
||||
});
|
||||
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
// added extra checks here in case wrong values appear use the default values rather than empty dashboards
|
||||
const supportedOrderColumnKeys = ['createdAt', 'updatedAt'];
|
||||
|
||||
@ -270,18 +276,24 @@ export function DashboardProvider({
|
||||
setIsDashboardFetching(true);
|
||||
try {
|
||||
return await getDashboard({
|
||||
uuid: dashboardId,
|
||||
id: dashboardId,
|
||||
});
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
return;
|
||||
} finally {
|
||||
setIsDashboardFetching(false);
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
onSuccess: (data) => {
|
||||
const updatedDashboardData = transformDashboardVariables(data);
|
||||
onError: (error) => {
|
||||
showErrorModal(error as APIError);
|
||||
},
|
||||
onSuccess: (data: SuccessResponseV2<Dashboard>) => {
|
||||
const updatedDashboardData = transformDashboardVariables(data?.data);
|
||||
const updatedDate = dayjs(updatedDashboardData.updatedAt);
|
||||
|
||||
setIsDashboardLocked(updatedDashboardData?.isLocked || false);
|
||||
setIsDashboardLocked(updatedDashboardData?.locked || false);
|
||||
|
||||
// on first render
|
||||
if (updatedTimeRef.current === null) {
|
||||
@ -387,29 +399,25 @@ export function DashboardProvider({
|
||||
setIsDashboardSlider(value);
|
||||
};
|
||||
|
||||
const handleError = useAxiosError();
|
||||
|
||||
const { mutate: lockDashboard } = useMutation(lockDashboardApi, {
|
||||
onSuccess: () => {
|
||||
const { mutate: lockDashboard } = useMutation(locked, {
|
||||
onSuccess: (_, props) => {
|
||||
setIsDashboardSlider(false);
|
||||
setIsDashboardLocked(true);
|
||||
setIsDashboardLocked(props.lock);
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
|
||||
const { mutate: unlockDashboard } = useMutation(unlockDashboardApi, {
|
||||
onSuccess: () => {
|
||||
setIsDashboardLocked(false);
|
||||
onError: (error) => {
|
||||
showErrorModal(error as APIError);
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
|
||||
const handleDashboardLockToggle = async (value: boolean): Promise<void> => {
|
||||
if (selectedDashboard) {
|
||||
if (value) {
|
||||
lockDashboard(selectedDashboard);
|
||||
} else {
|
||||
unlockDashboard(selectedDashboard);
|
||||
try {
|
||||
await lockDashboard({
|
||||
id: selectedDashboard.id,
|
||||
lock: value,
|
||||
});
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
export interface DashboardSortOrder {
|
||||
@ -19,7 +20,7 @@ export interface IDashboardContext {
|
||||
isDashboardLocked: boolean;
|
||||
handleToggleDashboardSlider: (value: boolean) => void;
|
||||
handleDashboardLockToggle: (value: boolean) => void;
|
||||
dashboardResponse: UseQueryResult<Dashboard, unknown>;
|
||||
dashboardResponse: UseQueryResult<SuccessResponseV2<Dashboard>, unknown>;
|
||||
selectedDashboard: Dashboard | undefined;
|
||||
dashboardId: string;
|
||||
layouts: Layout[];
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Dashboard, DashboardData } from './getAll';
|
||||
import { Dashboard } from './getAll';
|
||||
|
||||
export type Props =
|
||||
| {
|
||||
title: Dashboard['data']['title'];
|
||||
uploadedGrafana: boolean;
|
||||
version?: string;
|
||||
}
|
||||
| { DashboardData: DashboardData; uploadedGrafana: boolean };
|
||||
export type Props = {
|
||||
title: Dashboard['data']['title'];
|
||||
uploadedGrafana: boolean;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
export type PayloadProps = Dashboard;
|
||||
export interface PayloadProps {
|
||||
data: Dashboard;
|
||||
status: string;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Dashboard } from './getAll';
|
||||
|
||||
export type Props = {
|
||||
uuid: Dashboard['uuid'];
|
||||
id: Dashboard['id'];
|
||||
};
|
||||
|
||||
export interface PayloadProps {
|
||||
status: 'success';
|
||||
status: string;
|
||||
data: null;
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { Dashboard } from './getAll';
|
||||
|
||||
export type Props = {
|
||||
uuid: Dashboard['uuid'];
|
||||
id: Dashboard['id'];
|
||||
};
|
||||
|
||||
export type PayloadProps = Dashboard;
|
||||
export interface PayloadProps {
|
||||
data: Dashboard;
|
||||
status: string;
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { IField } from '../logs/fields';
|
||||
import { BaseAutocompleteData } from '../queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
export type PayloadProps = Dashboard[];
|
||||
|
||||
export const VariableQueryTypeArr = ['QUERY', 'TEXTBOX', 'CUSTOM'] as const;
|
||||
export type TVariableQueryType = typeof VariableQueryTypeArr[number];
|
||||
|
||||
@ -50,14 +48,18 @@ export interface IDashboardVariable {
|
||||
change?: boolean;
|
||||
}
|
||||
export interface Dashboard {
|
||||
id: number;
|
||||
uuid: string;
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdBy: string;
|
||||
updatedBy: string;
|
||||
data: DashboardData;
|
||||
isLocked?: boolean;
|
||||
locked?: boolean;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: Dashboard[];
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface DashboardTemplate {
|
||||
@ -69,7 +71,7 @@ export interface DashboardTemplate {
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
uuid?: string;
|
||||
// uuid?: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
name?: string;
|
||||
|
11
frontend/src/types/api/dashboard/lockUnlock.ts
Normal file
11
frontend/src/types/api/dashboard/lockUnlock.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Dashboard } from './getAll';
|
||||
|
||||
export type Props = {
|
||||
id: Dashboard['id'];
|
||||
lock: boolean;
|
||||
};
|
||||
|
||||
export interface PayloadProps {
|
||||
data: null;
|
||||
status: string;
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
import { Dashboard, DashboardData } from './getAll';
|
||||
|
||||
export type Props = {
|
||||
uuid: Dashboard['uuid'];
|
||||
id: Dashboard['id'];
|
||||
data: DashboardData;
|
||||
};
|
||||
|
||||
export type PayloadProps = Dashboard;
|
||||
export interface PayloadProps {
|
||||
data: Dashboard;
|
||||
status: string;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ export enum LicenseEvent {
|
||||
export enum LicenseStatus {
|
||||
SUSPENDED = 'SUSPENDED',
|
||||
VALID = 'VALID',
|
||||
INVALID = 'INVALID',
|
||||
}
|
||||
|
||||
export enum LicenseState {
|
||||
|
2
go.mod
2
go.mod
@ -26,7 +26,6 @@ require (
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/gosimple/slug v1.10.0
|
||||
github.com/huandu/go-sqlbuilder v1.35.0
|
||||
github.com/jackc/pgx/v5 v5.7.2
|
||||
github.com/jmoiron/sqlx v1.3.4
|
||||
@ -138,7 +137,6 @@ require (
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/gosimple/unidecode v1.0.0 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -450,10 +450,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gosimple/slug v1.10.0 h1:3XbiQua1IpCdrvuntWvGBxVm+K99wCSxJjlxkP49GGQ=
|
||||
github.com/gosimple/slug v1.10.0/go.mod h1:MICb3w495l9KNdZm+Xn5b6T2Hn831f9DMxiJ1r+bAjw=
|
||||
github.com/gosimple/unidecode v1.0.0 h1:kPdvM+qy0tnk4/BrnkrbdJ82xe88xn7c9hcaipDz4dQ=
|
||||
github.com/gosimple/unidecode v1.0.0/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
|
@ -67,23 +67,6 @@ func (store *config) Set(ctx context.Context, config *alertmanagertypes.Config,
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
func (store *config) ListOrgs(ctx context.Context) ([]string, error) {
|
||||
var orgIDs []string
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Table("organizations").
|
||||
ColumnExpr("id").
|
||||
Scan(ctx, &orgIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return orgIDs, nil
|
||||
}
|
||||
|
||||
func (store *config) CreateChannel(ctx context.Context, channel *alertmanagertypes.Channel, opts ...alertmanagertypes.StoreOption) error {
|
||||
return store.wrap(ctx, func(ctx context.Context) error {
|
||||
if _, err := store.
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerbatcher"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerstore/sqlalertmanagerstore"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@ -57,16 +58,17 @@ type provider struct {
|
||||
configStore alertmanagertypes.ConfigStore
|
||||
batcher *alertmanagerbatcher.Batcher
|
||||
url *url.URL
|
||||
orgGetter organization.Getter
|
||||
orgID string
|
||||
}
|
||||
|
||||
func NewFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[alertmanager.Alertmanager, alertmanager.Config] {
|
||||
func NewFactory(sqlstore sqlstore.SQLStore, orgGetter organization.Getter) factory.ProviderFactory[alertmanager.Alertmanager, alertmanager.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("legacy"), func(ctx context.Context, settings factory.ProviderSettings, config alertmanager.Config) (alertmanager.Alertmanager, error) {
|
||||
return New(ctx, settings, config, sqlstore)
|
||||
return New(ctx, settings, config, sqlstore, orgGetter)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config alertmanager.Config, sqlstore sqlstore.SQLStore) (*provider, error) {
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config alertmanager.Config, sqlstore sqlstore.SQLStore, orgGetter organization.Getter) (*provider, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/alertmanager/legacyalertmanager")
|
||||
configStore := sqlalertmanagerstore.NewConfigStore(sqlstore)
|
||||
|
||||
@ -92,7 +94,7 @@ func (provider *provider) Start(ctx context.Context) error {
|
||||
// For the first time, we need to get the orgID from the config store.
|
||||
// Since this is the legacy alertmanager, we get the first org from the store.
|
||||
if provider.orgID == "" {
|
||||
orgIDs, err := provider.configStore.ListOrgs(ctx)
|
||||
orgIDs, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to send alerts to alertmanager", "error", err)
|
||||
continue
|
||||
@ -103,7 +105,7 @@ func (provider *provider) Start(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
|
||||
provider.orgID = orgIDs[0]
|
||||
provider.orgID = orgIDs[0].ID.String()
|
||||
}
|
||||
|
||||
if err := provider.putAlerts(ctx, provider.orgID, alerts); err != nil {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
)
|
||||
|
||||
@ -20,6 +21,9 @@ type Service struct {
|
||||
// configStore is the config store for the alertmanager service
|
||||
configStore alertmanagertypes.ConfigStore
|
||||
|
||||
// organization is the organization module for the alertmanager service
|
||||
orgGetter organization.Getter
|
||||
|
||||
// settings is the settings for the alertmanager service
|
||||
settings factory.ScopedProviderSettings
|
||||
|
||||
@ -30,11 +34,19 @@ type Service struct {
|
||||
serversMtx sync.RWMutex
|
||||
}
|
||||
|
||||
func New(ctx context.Context, settings factory.ScopedProviderSettings, config alertmanagerserver.Config, stateStore alertmanagertypes.StateStore, configStore alertmanagertypes.ConfigStore) *Service {
|
||||
func New(
|
||||
ctx context.Context,
|
||||
settings factory.ScopedProviderSettings,
|
||||
config alertmanagerserver.Config,
|
||||
stateStore alertmanagertypes.StateStore,
|
||||
configStore alertmanagertypes.ConfigStore,
|
||||
orgGetter organization.Getter,
|
||||
) *Service {
|
||||
service := &Service{
|
||||
config: config,
|
||||
stateStore: stateStore,
|
||||
configStore: configStore,
|
||||
orgGetter: orgGetter,
|
||||
settings: settings,
|
||||
servers: make(map[string]*alertmanagerserver.Server),
|
||||
serversMtx: sync.RWMutex{},
|
||||
@ -44,38 +56,38 @@ func New(ctx context.Context, settings factory.ScopedProviderSettings, config al
|
||||
}
|
||||
|
||||
func (service *Service) SyncServers(ctx context.Context) error {
|
||||
orgIDs, err := service.configStore.ListOrgs(ctx)
|
||||
orgs, err := service.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.serversMtx.Lock()
|
||||
for _, orgID := range orgIDs {
|
||||
config, err := service.getConfig(ctx, orgID)
|
||||
for _, org := range orgs {
|
||||
config, err := service.getConfig(ctx, org.ID.StringValue())
|
||||
if err != nil {
|
||||
service.settings.Logger().ErrorContext(ctx, "failed to get alertmanager config for org", "org_id", orgID, "error", err)
|
||||
service.settings.Logger().ErrorContext(ctx, "failed to get alertmanager config for org", "org_id", org.ID.StringValue(), "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// If the server is not present, create it and sync the config
|
||||
if _, ok := service.servers[orgID]; !ok {
|
||||
server, err := service.newServer(ctx, orgID)
|
||||
if _, ok := service.servers[org.ID.StringValue()]; !ok {
|
||||
server, err := service.newServer(ctx, org.ID.StringValue())
|
||||
if err != nil {
|
||||
service.settings.Logger().ErrorContext(ctx, "failed to create alertmanager server", "org_id", orgID, "error", err)
|
||||
service.settings.Logger().ErrorContext(ctx, "failed to create alertmanager server", "org_id", org.ID.StringValue(), "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
service.servers[orgID] = server
|
||||
service.servers[org.ID.StringValue()] = server
|
||||
}
|
||||
|
||||
if service.servers[orgID].Hash() == config.StoreableConfig().Hash {
|
||||
service.settings.Logger().DebugContext(ctx, "skipping alertmanager sync for org", "org_id", orgID, "hash", config.StoreableConfig().Hash)
|
||||
if service.servers[org.ID.StringValue()].Hash() == config.StoreableConfig().Hash {
|
||||
service.settings.Logger().DebugContext(ctx, "skipping alertmanager sync for org", "org_id", org.ID.StringValue(), "hash", config.StoreableConfig().Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
err = service.servers[orgID].SetConfig(ctx, config)
|
||||
err = service.servers[org.ID.StringValue()].SetConfig(ctx, config)
|
||||
if err != nil {
|
||||
service.settings.Logger().ErrorContext(ctx, "failed to set config for alertmanager server", "org_id", orgID, "error", err)
|
||||
service.settings.Logger().ErrorContext(ctx, "failed to set config for alertmanager server", "org_id", org.ID.StringValue(), "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerstore/sqlalertmanagerstore"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@ -22,13 +23,13 @@ type provider struct {
|
||||
stopC chan struct{}
|
||||
}
|
||||
|
||||
func NewFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[alertmanager.Alertmanager, alertmanager.Config] {
|
||||
func NewFactory(sqlstore sqlstore.SQLStore, orgGetter organization.Getter) factory.ProviderFactory[alertmanager.Alertmanager, alertmanager.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, settings factory.ProviderSettings, config alertmanager.Config) (alertmanager.Alertmanager, error) {
|
||||
return New(ctx, settings, config, sqlstore)
|
||||
return New(ctx, settings, config, sqlstore, orgGetter)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config alertmanager.Config, sqlstore sqlstore.SQLStore) (*provider, error) {
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config alertmanager.Config, sqlstore sqlstore.SQLStore, orgGetter organization.Getter) (*provider, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager")
|
||||
configStore := sqlalertmanagerstore.NewConfigStore(sqlstore)
|
||||
stateStore := sqlalertmanagerstore.NewStateStore(sqlstore)
|
||||
@ -40,6 +41,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
|
||||
config.Signoz.Config,
|
||||
stateStore,
|
||||
configStore,
|
||||
orgGetter,
|
||||
),
|
||||
settings: settings,
|
||||
config: config,
|
||||
|
@ -5,9 +5,15 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
const (
|
||||
apiKeyCrossOrgMessage string = "::API-KEY-CROSS-ORG::"
|
||||
)
|
||||
|
||||
type APIKey struct {
|
||||
@ -15,10 +21,11 @@ type APIKey struct {
|
||||
uuid *authtypes.UUID
|
||||
headers []string
|
||||
logger *slog.Logger
|
||||
sharder sharder.Sharder
|
||||
}
|
||||
|
||||
func NewAPIKey(store sqlstore.SQLStore, headers []string, logger *slog.Logger) *APIKey {
|
||||
return &APIKey{store: store, uuid: authtypes.NewUUID(), headers: headers, logger: logger}
|
||||
func NewAPIKey(store sqlstore.SQLStore, headers []string, logger *slog.Logger, sharder sharder.Sharder) *APIKey {
|
||||
return &APIKey{store: store, uuid: authtypes.NewUUID(), headers: headers, logger: logger, sharder: sharder}
|
||||
}
|
||||
|
||||
func (a *APIKey) Wrap(next http.Handler) http.Handler {
|
||||
@ -36,13 +43,20 @@ func (a *APIKey) Wrap(next http.Handler) http.Handler {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
apiKeyToken, ok := authtypes.UUIDFromContext(ctx)
|
||||
if !ok {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.store.BunDB().NewSelect().Model(&apiKey).Where("token = ?", apiKeyToken).Scan(r.Context())
|
||||
err = a.
|
||||
store.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&apiKey).
|
||||
Where("token = ?", apiKeyToken).
|
||||
Scan(r.Context())
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
@ -71,6 +85,18 @@ func (a *APIKey) Wrap(next http.Handler) http.Handler {
|
||||
|
||||
ctx = authtypes.NewContextWithClaims(ctx, jwt)
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.sharder.IsMyOwnedKey(r.Context(), types.NewOrganizationKey(valuer.MustNewUUID(claims.OrgID))); err != nil {
|
||||
a.logger.ErrorContext(r.Context(), apiKeyCrossOrgMessage, "claims", claims, "error", err)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
@ -1,18 +1,28 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
const (
|
||||
authCrossOrgMessage string = "::AUTH-CROSS-ORG::"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
jwt *authtypes.JWT
|
||||
headers []string
|
||||
sharder sharder.Sharder
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewAuth(jwt *authtypes.JWT, headers []string) *Auth {
|
||||
return &Auth{jwt: jwt, headers: headers}
|
||||
func NewAuth(jwt *authtypes.JWT, headers []string, sharder sharder.Sharder, logger *slog.Logger) *Auth {
|
||||
return &Auth{jwt: jwt, headers: headers, sharder: sharder, logger: logger}
|
||||
}
|
||||
|
||||
func (a *Auth) Wrap(next http.Handler) http.Handler {
|
||||
@ -28,6 +38,18 @@ func (a *Auth) Wrap(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.sharder.IsMyOwnedKey(r.Context(), types.NewOrganizationKey(valuer.MustNewUUID(claims.OrgID))); err != nil {
|
||||
a.logger.ErrorContext(r.Context(), authCrossOrgMessage, "claims", claims, "error", err)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
@ -4,13 +4,13 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/apdextypes"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
Get(context.Context, string, []string) ([]*types.ApdexSettings, error)
|
||||
Get(context.Context, string, []string) ([]*apdextypes.Settings, error)
|
||||
|
||||
Set(context.Context, string, *types.ApdexSettings) error
|
||||
Set(context.Context, string, *apdextypes.Settings) error
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/apdextypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
@ -31,7 +31,7 @@ func (handler *handler) Set(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var apdexSettings types.ApdexSettings
|
||||
var apdexSettings apdextypes.Settings
|
||||
if err := json.NewDecoder(req.Body).Decode(&apdexSettings); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/apdextypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@ -25,8 +25,8 @@ func NewModule(sqlstore sqlstore.SQLStore) apdex.Module {
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID string, services []string) ([]*types.ApdexSettings, error) {
|
||||
var apdexSettings []*types.ApdexSettings
|
||||
func (module *module) Get(ctx context.Context, orgID string, services []string) ([]*apdextypes.Settings, error) {
|
||||
var apdexSettings []*apdextypes.Settings
|
||||
|
||||
err := module.
|
||||
sqlstore.
|
||||
@ -51,7 +51,7 @@ func (module *module) Get(ctx context.Context, orgID string, services []string)
|
||||
}
|
||||
|
||||
if !found {
|
||||
apdexSettings = append(apdexSettings, &types.ApdexSettings{
|
||||
apdexSettings = append(apdexSettings, &apdextypes.Settings{
|
||||
ServiceName: service,
|
||||
Threshold: defaultApdexThreshold,
|
||||
})
|
||||
@ -61,7 +61,7 @@ func (module *module) Get(ctx context.Context, orgID string, services []string)
|
||||
return apdexSettings, nil
|
||||
}
|
||||
|
||||
func (module *module) Set(ctx context.Context, orgID string, apdexSettings *types.ApdexSettings) error {
|
||||
func (module *module) Set(ctx context.Context, orgID string, apdexSettings *apdextypes.Settings) error {
|
||||
apdexSettings.OrgID = orgID
|
||||
apdexSettings.Identifiable.ID = valuer.GenerateUUID()
|
||||
|
||||
|
@ -4,25 +4,32 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
Create(ctx context.Context, orgID string, email string, data map[string]interface{}) (*types.Dashboard, error)
|
||||
Create(ctx context.Context, orgID valuer.UUID, createdBy string, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
List(ctx context.Context, orgID string) ([]*types.Dashboard, error)
|
||||
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
Delete(ctx context.Context, orgID, uuid string) error
|
||||
List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error)
|
||||
|
||||
Get(ctx context.Context, orgID, uuid string) (*types.Dashboard, error)
|
||||
Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboard) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
GetByMetricNames(ctx context.Context, orgID string, metricNames []string) (map[string][]map[string]string, error)
|
||||
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, lock bool) error
|
||||
|
||||
Update(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}) (*types.Dashboard, error)
|
||||
Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
LockUnlock(ctx context.Context, orgID, uuid string, lock bool) error
|
||||
GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error)
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
Create(http.ResponseWriter, *http.Request)
|
||||
|
||||
Update(http.ResponseWriter, *http.Request)
|
||||
|
||||
LockUnlock(http.ResponseWriter, *http.Request)
|
||||
|
||||
Delete(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
@ -2,12 +2,16 @@ package impldashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@ -19,8 +23,8 @@ func NewHandler(module dashboard.Module) dashboard.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) Delete(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
@ -29,13 +33,151 @@ func (handler *handler) Delete(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
uuid := mux.Vars(req)["uuid"]
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.Delete(ctx, claims.OrgID, uuid)
|
||||
req := dashboardtypes.PostableDashboard{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.Create(ctx, orgID, claims.Email, req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
gettableDashboard, err := dashboardtypes.NewGettableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, gettableDashboard)
|
||||
}
|
||||
|
||||
func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id := mux.Vars(r)["id"]
|
||||
if id == "" {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is missing in the path"))
|
||||
return
|
||||
}
|
||||
dashboardID, err := valuer.NewUUID(id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := dashboardtypes.UpdatableDashboard{}
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.Update(ctx, orgID, dashboardID, claims.Email, req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
func (handler *handler) LockUnlock(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id := mux.Vars(r)["id"]
|
||||
if id == "" {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is missing in the path"))
|
||||
return
|
||||
}
|
||||
dashboardID, err := valuer.NewUUID(id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(dashboardtypes.LockUnlockDashboard)
|
||||
err = json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.LockUnlock(ctx, orgID, dashboardID, claims.Email, *req.Locked)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, nil)
|
||||
|
||||
}
|
||||
|
||||
func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id := mux.Vars(r)["id"]
|
||||
if id == "" {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is missing in the path"))
|
||||
return
|
||||
}
|
||||
dashboardID, err := valuer.NewUUID(id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
err = handler.module.Delete(ctx, orgID, dashboardID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
@ -2,164 +2,40 @@ package impldashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/google/uuid"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
store dashboardtypes.Store
|
||||
settings factory.ScopedProviderSettings
|
||||
}
|
||||
|
||||
func NewModule(sqlstore sqlstore.SQLStore) dashboard.Module {
|
||||
func NewModule(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) dashboard.Module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/impldashboard")
|
||||
return &module{
|
||||
sqlstore: sqlstore,
|
||||
store: NewStore(sqlstore),
|
||||
settings: scopedProviderSettings,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDashboard creates a new dashboard
|
||||
func (module *module) Create(ctx context.Context, orgID string, email string, data map[string]interface{}) (*types.Dashboard, error) {
|
||||
dash := &types.Dashboard{
|
||||
Data: data,
|
||||
}
|
||||
|
||||
dash.OrgID = orgID
|
||||
dash.CreatedAt = time.Now()
|
||||
dash.CreatedBy = email
|
||||
dash.UpdatedAt = time.Now()
|
||||
dash.UpdatedBy = email
|
||||
dash.UpdateSlug()
|
||||
dash.UUID = uuid.New().String()
|
||||
if data["uuid"] != nil {
|
||||
dash.UUID = data["uuid"].(string)
|
||||
}
|
||||
|
||||
err := module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewInsert().
|
||||
Model(dash).
|
||||
Returning("id").
|
||||
Scan(ctx, &dash.ID)
|
||||
if err != nil {
|
||||
return nil, module.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "dashboard with uuid %s already exists", dash.UUID)
|
||||
}
|
||||
|
||||
return dash, nil
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID string) ([]*types.Dashboard, error) {
|
||||
dashboards := []*types.Dashboard{}
|
||||
|
||||
err := module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&dashboards).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, postableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboards, nil
|
||||
}
|
||||
|
||||
func (module *module) Delete(ctx context.Context, orgID, uuid string) error {
|
||||
dashboard, err := module.Get(ctx, orgID, uuid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be able to delete it")
|
||||
}
|
||||
|
||||
result, err := module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewDelete().
|
||||
Model(&types.Dashboard{}).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("uuid = ?", uuid).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
affectedRows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affectedRows == 0 {
|
||||
return errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "no dashboard found with uuid: %s", uuid)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID, uuid string) (*types.Dashboard, error) {
|
||||
dashboard := types.Dashboard{}
|
||||
err := module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&dashboard).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("uuid = ?", uuid).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, module.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with uuid %s not found", uuid)
|
||||
}
|
||||
|
||||
return &dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) Update(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}) (*types.Dashboard, error) {
|
||||
mapData, err := json.Marshal(data)
|
||||
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboard, err := module.Get(ctx, orgID, uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be able to edit it")
|
||||
}
|
||||
|
||||
// if the total count of panels has reduced by more than 1,
|
||||
// return error
|
||||
existingIds := getWidgetIds(dashboard.Data)
|
||||
newIds := getWidgetIds(data)
|
||||
|
||||
differenceIds := getIdDifference(existingIds, newIds)
|
||||
|
||||
if len(differenceIds) > 1 {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "deleting more than one panel is not supported")
|
||||
}
|
||||
|
||||
dashboard.UpdatedAt = time.Now()
|
||||
dashboard.UpdatedBy = userEmail
|
||||
dashboard.Data = data
|
||||
|
||||
_, err = module.sqlstore.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(dashboard).
|
||||
Set("updated_at = ?", dashboard.UpdatedAt).
|
||||
Set("updated_by = ?", userEmail).
|
||||
Set("data = ?", mapData).
|
||||
Where("uuid = ?", dashboard.UUID).Exec(ctx)
|
||||
err = module.store.Create(ctx, storableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -167,28 +43,73 @@ func (module *module) Update(ctx context.Context, orgID, userEmail, uuid string,
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID, uuid string, lock bool) error {
|
||||
dashboard, err := module.Get(ctx, orgID, uuid)
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
storableDashboard, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboard, err := dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
storableDashboards, err := module.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboards, err := dashboardtypes.NewDashboardsFromStorableDashboards(storableDashboards)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboards, nil
|
||||
}
|
||||
|
||||
func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, updatableDashboard dashboardtypes.UpdatableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dashboard.Update(updatableDashboard, updatedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = module.store.Update(ctx, orgID, storableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, lock bool) error {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var lockValue int
|
||||
if lock {
|
||||
lockValue = 1
|
||||
} else {
|
||||
lockValue = 0
|
||||
err = dashboard.LockUnlock(ctx, lock, updatedBy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(dashboard).
|
||||
Set("locked = ?", lockValue).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("uuid = ?", uuid).
|
||||
Exec(ctx)
|
||||
err = module.store.Update(ctx, orgID, storableDashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -196,15 +117,21 @@ func (module *module) LockUnlock(ctx context.Context, orgID, uuid string, lock b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) GetByMetricNames(ctx context.Context, orgID string, metricNames []string) (map[string][]map[string]string, error) {
|
||||
dashboards := []types.Dashboard{}
|
||||
err := module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&dashboards).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashboard.Locked {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
|
||||
}
|
||||
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error) {
|
||||
dashboards, err := module.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -266,7 +193,7 @@ func (module *module) GetByMetricNames(ctx context.Context, orgID string, metric
|
||||
for _, metricName := range metricNames {
|
||||
if strings.TrimSpace(key) == metricName {
|
||||
result[metricName] = append(result[metricName], map[string]string{
|
||||
"dashboard_id": dashboard.UUID,
|
||||
"dashboard_id": dashboard.ID,
|
||||
"widget_name": widgetTitle,
|
||||
"widget_id": widgetID,
|
||||
"dashboard_name": dashTitle,
|
||||
@ -280,52 +207,3 @@ func (module *module) GetByMetricNames(ctx context.Context, orgID string, metric
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getWidgetIds(data map[string]interface{}) []string {
|
||||
widgetIds := []string{}
|
||||
if data != nil && data["widgets"] != nil {
|
||||
widgets, ok := data["widgets"]
|
||||
if ok {
|
||||
data, ok := widgets.([]interface{})
|
||||
if ok {
|
||||
for _, widget := range data {
|
||||
sData, ok := widget.(map[string]interface{})
|
||||
if ok && sData["query"] != nil && sData["id"] != nil {
|
||||
id, ok := sData["id"].(string)
|
||||
|
||||
if ok {
|
||||
widgetIds = append(widgetIds, id)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return widgetIds
|
||||
}
|
||||
|
||||
func getIdDifference(existingIds []string, newIds []string) []string {
|
||||
// Convert newIds array to a map for faster lookups
|
||||
newIdsMap := make(map[string]bool)
|
||||
for _, id := range newIds {
|
||||
newIdsMap[id] = true
|
||||
}
|
||||
|
||||
// Initialize a map to keep track of elements in the difference array
|
||||
differenceMap := make(map[string]bool)
|
||||
|
||||
// Initialize the difference array
|
||||
difference := []string{}
|
||||
|
||||
// Iterate through existingIds
|
||||
for _, id := range existingIds {
|
||||
// If the id is not found in newIds, and it's not already in the difference array
|
||||
if _, found := newIdsMap[id]; !found && !differenceMap[id] {
|
||||
difference = append(difference, id)
|
||||
differenceMap[id] = true // Mark the id as seen in the difference array
|
||||
}
|
||||
}
|
||||
|
||||
return difference
|
||||
}
|
||||
|
99
pkg/modules/dashboard/impldashboard/store.go
Normal file
99
pkg/modules/dashboard/impldashboard/store.go
Normal file
@ -0,0 +1,99 @@
|
||||
package impldashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) dashboardtypes.Store {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) Create(ctx context.Context, storabledashboard *dashboardtypes.StorableDashboard) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewInsert().
|
||||
Model(storabledashboard).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "dashboard with id %s already exists", storabledashboard.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.StorableDashboard, error) {
|
||||
storableDashboard := new(dashboardtypes.StorableDashboard)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(storableDashboard).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with id %s doesn't exist", id)
|
||||
}
|
||||
|
||||
return storableDashboard, nil
|
||||
}
|
||||
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.StorableDashboard, error) {
|
||||
storableDashboards := make([]*dashboardtypes.StorableDashboard, 0)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&storableDashboards).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "no dashboards found in orgID %s", orgID)
|
||||
}
|
||||
|
||||
return storableDashboards, nil
|
||||
}
|
||||
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, storableDashboard *dashboardtypes.StorableDashboard) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(storableDashboard).
|
||||
WherePK().
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeAlreadyExists, "dashboard with id %s doesn't exist", storableDashboard.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewDelete().
|
||||
Model(new(dashboardtypes.StorableDashboard)).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with id %s doesn't exist", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
36
pkg/modules/organization/implorganization/getter.go
Normal file
36
pkg/modules/organization/implorganization/getter.go
Normal file
@ -0,0 +1,36 @@
|
||||
package implorganization
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type getter struct {
|
||||
store types.OrganizationStore
|
||||
sharder sharder.Sharder
|
||||
}
|
||||
|
||||
func NewGetter(store types.OrganizationStore, sharder sharder.Sharder) organization.Getter {
|
||||
return &getter{store: store, sharder: sharder}
|
||||
}
|
||||
|
||||
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.Organization, error) {
|
||||
return module.store.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (module *getter) List(ctx context.Context) ([]*types.Organization, error) {
|
||||
return module.store.GetAll(ctx)
|
||||
}
|
||||
|
||||
func (module *getter) ListByOwnedKeyRange(ctx context.Context) ([]*types.Organization, error) {
|
||||
start, end, err := module.sharder.GetMyOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return module.store.ListByKeyRange(ctx, start, end)
|
||||
}
|
@ -15,11 +15,12 @@ import (
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
module organization.Module
|
||||
orgGetter organization.Getter
|
||||
orgSetter organization.Setter
|
||||
}
|
||||
|
||||
func NewHandler(module organization.Module) organization.Handler {
|
||||
return &handler{module: module}
|
||||
func NewHandler(orgGetter organization.Getter, orgSetter organization.Setter) organization.Handler {
|
||||
return &handler{orgGetter: orgGetter, orgSetter: orgSetter}
|
||||
}
|
||||
|
||||
func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
@ -38,7 +39,7 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := handler.module.Get(ctx, orgID)
|
||||
organization, err := handler.orgGetter.Get(ctx, orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@ -67,10 +68,11 @@ func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req.ID = orgID
|
||||
err = handler.module.Update(ctx, req)
|
||||
err = handler.orgSetter.Update(ctx, req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
@ -1,33 +0,0 @@
|
||||
package implorganization
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store types.OrganizationStore
|
||||
}
|
||||
|
||||
func NewModule(organizationStore types.OrganizationStore) organization.Module {
|
||||
return &module{store: organizationStore}
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, organization *types.Organization) error {
|
||||
return module.store.Create(ctx, organization)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, id valuer.UUID) (*types.Organization, error) {
|
||||
return module.store.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (module *module) GetAll(ctx context.Context) ([]*types.Organization, error) {
|
||||
return module.store.GetAll(ctx)
|
||||
}
|
||||
|
||||
func (module *module) Update(ctx context.Context, updatedOrganization *types.Organization) error {
|
||||
return module.store.Update(ctx, updatedOrganization)
|
||||
}
|
40
pkg/modules/organization/implorganization/setter.go
Normal file
40
pkg/modules/organization/implorganization/setter.go
Normal file
@ -0,0 +1,40 @@
|
||||
package implorganization
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
)
|
||||
|
||||
type setter struct {
|
||||
store types.OrganizationStore
|
||||
alertmanager alertmanager.Alertmanager
|
||||
quickfilter quickfilter.Module
|
||||
}
|
||||
|
||||
func NewSetter(store types.OrganizationStore, alertmanager alertmanager.Alertmanager, quickfilter quickfilter.Module) organization.Setter {
|
||||
return &setter{store: store, alertmanager: alertmanager, quickfilter: quickfilter}
|
||||
}
|
||||
|
||||
func (module *setter) Create(ctx context.Context, organization *types.Organization) error {
|
||||
if err := module.store.Create(ctx, organization); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.alertmanager.SetDefaultConfig(ctx, organization.ID.StringValue()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.quickfilter.SetDefaultConfig(ctx, organization.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *setter) Update(ctx context.Context, updatedOrganization *types.Organization) error {
|
||||
return module.store.Update(ctx, updatedOrganization)
|
||||
}
|
@ -92,3 +92,20 @@ func (store *store) Delete(ctx context.Context, id valuer.UUID) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) ListByKeyRange(ctx context.Context, start, end uint32) ([]*types.Organization, error) {
|
||||
organizations := make([]*types.Organization, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&organizations).
|
||||
Where("key >= ?", start).
|
||||
Where("key <= ?", end).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return organizations, nil
|
||||
}
|
||||
|
@ -8,17 +8,22 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
// Create creates the given organization
|
||||
Create(context.Context, *types.Organization) error
|
||||
|
||||
type Getter interface {
|
||||
// Get gets the organization based on the given id
|
||||
Get(context.Context, valuer.UUID) (*types.Organization, error)
|
||||
|
||||
// GetAll gets all the organizations
|
||||
GetAll(context.Context) ([]*types.Organization, error)
|
||||
// Lists all the organizations
|
||||
List(context.Context) ([]*types.Organization, error)
|
||||
|
||||
// Update updates the given organization based on id present
|
||||
// ListByOwnedKeyRange gets all the organizations owned by the instance
|
||||
ListByOwnedKeyRange(context.Context) ([]*types.Organization, error)
|
||||
}
|
||||
|
||||
type Setter interface {
|
||||
// Create creates the given organization
|
||||
Create(context.Context, *types.Organization) error
|
||||
|
||||
// Update updates the given organization
|
||||
Update(context.Context, *types.Organization) error
|
||||
}
|
||||
|
||||
|
235
pkg/modules/tracefunnel/impltracefunnel/handler.go
Normal file
235
pkg/modules/tracefunnel/impltracefunnel/handler.go
Normal file
@ -0,0 +1,235 @@
|
||||
package impltracefunnel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
tf "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
module tracefunnel.Module
|
||||
}
|
||||
|
||||
func NewHandler(module tracefunnel.Module) tracefunnel.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
|
||||
var req tf.PostableFunnel
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
funnel, err := handler.module.Create(r.Context(), req.Timestamp, req.Name, valuer.MustNewUUID(claims.UserID), valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to create funnel: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response := tf.ConstructFunnelResponse(funnel, &claims)
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
|
||||
var req tf.PostableFunnel
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
updatedAt, err := tf.ValidateAndConvertTimestamp(req.Timestamp)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
funnel, err := handler.module.Get(r.Context(), req.FunnelID, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"funnel not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
steps, err := tf.ProcessFunnelSteps(req.Steps)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
funnel.Steps = steps
|
||||
funnel.UpdatedAt = updatedAt
|
||||
funnel.UpdatedBy = claims.UserID
|
||||
|
||||
if req.Name != "" {
|
||||
funnel.Name = req.Name
|
||||
}
|
||||
if req.Description != "" {
|
||||
funnel.Description = req.Description
|
||||
}
|
||||
|
||||
if err := handler.module.Update(r.Context(), funnel, valuer.MustNewUUID(claims.UserID)); err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to update funnel in database: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
updatedFunnel, err := handler.module.Get(r.Context(), funnel.ID, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to get updated funnel: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response := tf.ConstructFunnelResponse(updatedFunnel, &claims)
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) {
|
||||
var req tf.PostableFunnel
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
updatedAt, err := tf.ValidateAndConvertTimestamp(req.Timestamp)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
funnelID := vars["funnel_id"]
|
||||
|
||||
funnel, err := handler.module.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"funnel not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
funnel.UpdatedAt = updatedAt
|
||||
funnel.UpdatedBy = claims.UserID
|
||||
|
||||
if req.Name != "" {
|
||||
funnel.Name = req.Name
|
||||
}
|
||||
if req.Description != "" {
|
||||
funnel.Description = req.Description
|
||||
}
|
||||
|
||||
if err := handler.module.Update(r.Context(), funnel, valuer.MustNewUUID(claims.UserID)); err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to update funnel in database: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
updatedFunnel, err := handler.module.Get(r.Context(), funnel.ID, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to get updated funnel: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response := tf.ConstructFunnelResponse(updatedFunnel, &claims)
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
funnels, err := handler.module.List(r.Context(), valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to list funnels: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
var response []tf.GettableFunnel
|
||||
for _, f := range funnels {
|
||||
response = append(response, tf.ConstructFunnelResponse(f, &claims))
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
funnelID := vars["funnel_id"]
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
funnel, err := handler.module.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"funnel not found: %v", err))
|
||||
return
|
||||
}
|
||||
response := tf.ConstructFunnelResponse(funnel, &claims)
|
||||
render.Success(rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
funnelID := vars["funnel_id"]
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := handler.module.Delete(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID)); err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to delete funnel: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, nil)
|
||||
}
|
173
pkg/modules/tracefunnel/impltracefunnel/handler_test.go
Normal file
173
pkg/modules/tracefunnel/impltracefunnel/handler_test.go
Normal file
@ -0,0 +1,173 @@
|
||||
package impltracefunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockModule struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockModule) Create(ctx context.Context, timestamp int64, name string, userID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
||||
args := m.Called(ctx, timestamp, name, userID, orgID)
|
||||
return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockModule) Get(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
||||
args := m.Called(ctx, funnelID, orgID)
|
||||
return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockModule) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID valuer.UUID) error {
|
||||
args := m.Called(ctx, funnel, userID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockModule) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
|
||||
args := m.Called(ctx, orgID)
|
||||
return args.Get(0).([]*traceFunnels.StorableFunnel), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockModule) Delete(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) error {
|
||||
args := m.Called(ctx, funnelID, orgID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockModule) Save(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID valuer.UUID, orgID valuer.UUID) error {
|
||||
args := m.Called(ctx, funnel, userID, orgID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockModule) GetFunnelMetadata(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (int64, int64, string, error) {
|
||||
args := m.Called(ctx, funnelID, orgID)
|
||||
return args.Get(0).(int64), args.Get(1).(int64), args.String(2), args.Error(3)
|
||||
}
|
||||
|
||||
func TestHandler_List(t *testing.T) {
|
||||
mockModule := new(MockModule)
|
||||
handler := NewHandler(mockModule)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/trace-funnels/list", nil)
|
||||
|
||||
orgID := valuer.GenerateUUID()
|
||||
claims := authtypes.Claims{
|
||||
OrgID: orgID.String(),
|
||||
}
|
||||
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), claims))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
funnel1ID := valuer.GenerateUUID()
|
||||
funnel2ID := valuer.GenerateUUID()
|
||||
expectedFunnels := []*traceFunnels.StorableFunnel{
|
||||
{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: funnel1ID,
|
||||
},
|
||||
Name: "funnel-1",
|
||||
OrgID: orgID,
|
||||
},
|
||||
{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: funnel2ID,
|
||||
},
|
||||
Name: "funnel-2",
|
||||
OrgID: orgID,
|
||||
},
|
||||
}
|
||||
|
||||
mockModule.On("List", req.Context(), orgID).Return(expectedFunnels, nil)
|
||||
|
||||
handler.List(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var response struct {
|
||||
Status string `json:"status"`
|
||||
Data []traceFunnels.GettableFunnel `json:"data"`
|
||||
}
|
||||
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "success", response.Status)
|
||||
assert.Len(t, response.Data, 2)
|
||||
assert.Equal(t, "funnel-1", response.Data[0].FunnelName)
|
||||
assert.Equal(t, "funnel-2", response.Data[1].FunnelName)
|
||||
|
||||
mockModule.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestHandler_Get(t *testing.T) {
|
||||
mockModule := new(MockModule)
|
||||
handler := NewHandler(mockModule)
|
||||
|
||||
funnelID := valuer.GenerateUUID()
|
||||
orgID := valuer.GenerateUUID()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/trace-funnels/"+funnelID.String(), nil)
|
||||
req = mux.SetURLVars(req, map[string]string{"funnel_id": funnelID.String()})
|
||||
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), authtypes.Claims{
|
||||
OrgID: orgID.String(),
|
||||
}))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
expectedFunnel := &traceFunnels.StorableFunnel{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: funnelID,
|
||||
},
|
||||
Name: "test-funnel",
|
||||
OrgID: orgID,
|
||||
}
|
||||
|
||||
mockModule.On("Get", req.Context(), funnelID, orgID).Return(expectedFunnel, nil)
|
||||
|
||||
handler.Get(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var response struct {
|
||||
Status string `json:"status"`
|
||||
Data traceFunnels.GettableFunnel `json:"data"`
|
||||
}
|
||||
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "success", response.Status)
|
||||
assert.Equal(t, "test-funnel", response.Data.FunnelName)
|
||||
assert.Equal(t, expectedFunnel.OrgID.String(), response.Data.OrgID)
|
||||
|
||||
mockModule.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestHandler_Delete(t *testing.T) {
|
||||
mockModule := new(MockModule)
|
||||
handler := NewHandler(mockModule)
|
||||
|
||||
funnelID := valuer.GenerateUUID()
|
||||
orgID := valuer.GenerateUUID()
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/trace-funnels/"+funnelID.String(), nil)
|
||||
req = mux.SetURLVars(req, map[string]string{"funnel_id": funnelID.String()})
|
||||
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), authtypes.Claims{
|
||||
OrgID: orgID.String(),
|
||||
}))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mockModule.On("Delete", req.Context(), funnelID, orgID).Return(nil)
|
||||
|
||||
handler.Delete(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
mockModule.AssertExpectations(t)
|
||||
}
|
96
pkg/modules/tracefunnel/impltracefunnel/module.go
Normal file
96
pkg/modules/tracefunnel/impltracefunnel/module.go
Normal file
@ -0,0 +1,96 @@
|
||||
package impltracefunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store traceFunnels.FunnelStore
|
||||
}
|
||||
|
||||
func NewModule(store traceFunnels.FunnelStore) tracefunnel.Module {
|
||||
return &module{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, timestamp int64, name string, userID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
||||
funnel := &traceFunnels.StorableFunnel{
|
||||
Name: name,
|
||||
OrgID: orgID,
|
||||
}
|
||||
funnel.CreatedAt = time.Unix(0, timestamp*1000000) // Convert to nanoseconds
|
||||
funnel.CreatedBy = userID.String()
|
||||
|
||||
// Set up the user relationship
|
||||
funnel.CreatedByUser = &types.User{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: userID,
|
||||
},
|
||||
}
|
||||
|
||||
if funnel.ID.IsZero() {
|
||||
funnel.ID = valuer.GenerateUUID()
|
||||
}
|
||||
|
||||
if funnel.CreatedAt.IsZero() {
|
||||
funnel.CreatedAt = time.Now()
|
||||
}
|
||||
if funnel.UpdatedAt.IsZero() {
|
||||
funnel.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
// Set created_by if CreatedByUser is present
|
||||
if funnel.CreatedByUser != nil {
|
||||
funnel.CreatedBy = funnel.CreatedByUser.Identifiable.ID.String()
|
||||
}
|
||||
|
||||
if err := module.store.Create(ctx, funnel); err != nil {
|
||||
return nil, fmt.Errorf("failed to create funnel: %v", err)
|
||||
}
|
||||
|
||||
return funnel, nil
|
||||
}
|
||||
|
||||
// Get gets a funnel by ID
|
||||
func (module *module) Get(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
||||
return module.store.Get(ctx, funnelID, orgID)
|
||||
}
|
||||
|
||||
// Update updates a funnel
|
||||
func (module *module) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID valuer.UUID) error {
|
||||
funnel.UpdatedBy = userID.String()
|
||||
return module.store.Update(ctx, funnel)
|
||||
}
|
||||
|
||||
// List lists all funnels for an organization
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
|
||||
funnels, err := module.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list funnels: %v", err)
|
||||
}
|
||||
|
||||
return funnels, nil
|
||||
}
|
||||
|
||||
// Delete deletes a funnel
|
||||
func (module *module) Delete(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) error {
|
||||
return module.store.Delete(ctx, funnelID, orgID)
|
||||
}
|
||||
|
||||
// GetFunnelMetadata gets metadata for a funnel
|
||||
func (module *module) GetFunnelMetadata(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (int64, int64, string, error) {
|
||||
funnel, err := module.store.Get(ctx, funnelID, orgID)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
|
||||
return funnel.CreatedAt.UnixNano() / 1000000, funnel.UpdatedAt.UnixNano() / 1000000, funnel.Description, nil
|
||||
}
|
114
pkg/modules/tracefunnel/impltracefunnel/store.go
Normal file
114
pkg/modules/tracefunnel/impltracefunnel/store.go
Normal file
@ -0,0 +1,114 @@
|
||||
package impltracefunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) traceFunnels.FunnelStore {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) Create(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
|
||||
// Check if a funnel with the same name already exists in the organization
|
||||
exists, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(new(traceFunnels.StorableFunnel)).
|
||||
Where("name = ? AND org_id = ?", funnel.Name, funnel.OrgID.String()).
|
||||
Exists(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to check for existing funnelr")
|
||||
}
|
||||
if exists {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(nil, traceFunnels.ErrFunnelAlreadyExists, "a funnel with name '%s' already exists in this organization", funnel.Name)
|
||||
}
|
||||
|
||||
_, err = store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewInsert().
|
||||
Model(funnel).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create funnels")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves a funnel by ID
|
||||
func (store *store) Get(ctx context.Context, uuid valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
||||
funnel := &traceFunnels.StorableFunnel{}
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(funnel).
|
||||
Relation("CreatedByUser").
|
||||
Where("?TableAlias.id = ? AND ?TableAlias.org_id = ?", uuid.String(), orgID.String()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get funnels")
|
||||
}
|
||||
return funnel, nil
|
||||
}
|
||||
|
||||
// Update updates an existing funnel
|
||||
func (store *store) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
|
||||
funnel.UpdatedAt = time.Now()
|
||||
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(funnel).
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, traceFunnels.ErrFunnelAlreadyExists, "a funnel with name '%s' already exists in this organization", funnel.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List retrieves all funnels for a given organization
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
|
||||
var funnels []*traceFunnels.StorableFunnel
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&funnels).
|
||||
Relation("CreatedByUser").
|
||||
Where("?TableAlias.org_id = ?", orgID.String()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to list funnels")
|
||||
}
|
||||
return funnels, nil
|
||||
}
|
||||
|
||||
// Delete removes a funnel by ID
|
||||
func (store *store) Delete(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewDelete().
|
||||
Model(new(traceFunnels.StorableFunnel)).
|
||||
Where("id = ? AND org_id = ?", funnelID.String(), orgID.String()).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete funnel")
|
||||
}
|
||||
return nil
|
||||
}
|
38
pkg/modules/tracefunnel/tracefunnel.go
Normal file
38
pkg/modules/tracefunnel/tracefunnel.go
Normal file
@ -0,0 +1,38 @@
|
||||
package tracefunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"net/http"
|
||||
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
)
|
||||
|
||||
// Module defines the interface for trace funnel operations
|
||||
type Module interface {
|
||||
Create(ctx context.Context, timestamp int64, name string, userID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error)
|
||||
|
||||
Get(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error)
|
||||
|
||||
Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID valuer.UUID) error
|
||||
|
||||
List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error)
|
||||
|
||||
Delete(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) error
|
||||
|
||||
GetFunnelMetadata(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (int64, int64, string, error)
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
New(http.ResponseWriter, *http.Request)
|
||||
|
||||
UpdateSteps(http.ResponseWriter, *http.Request)
|
||||
|
||||
UpdateFunnel(http.ResponseWriter, *http.Request)
|
||||
|
||||
List(http.ResponseWriter, *http.Request)
|
||||
|
||||
Get(http.ResponseWriter, *http.Request)
|
||||
|
||||
Delete(http.ResponseWriter, *http.Request)
|
||||
}
|
183
pkg/modules/tracefunnel/tracefunneltest/module_test.go
Normal file
183
pkg/modules/tracefunnel/tracefunneltest/module_test.go
Normal file
@ -0,0 +1,183 @@
|
||||
package tracefunneltest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel/impltracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockStore) Create(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
|
||||
args := m.Called(ctx, funnel)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockStore) Get(ctx context.Context, uuid valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
||||
args := m.Called(ctx, uuid, orgID)
|
||||
return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockStore) List(ctx context.Context, orgID valuer.UUID) ([]*traceFunnels.StorableFunnel, error) {
|
||||
args := m.Called(ctx, orgID)
|
||||
return args.Get(0).([]*traceFunnels.StorableFunnel), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockStore) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel) error {
|
||||
args := m.Called(ctx, funnel)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockStore) Delete(ctx context.Context, uuid valuer.UUID, orgID valuer.UUID) error {
|
||||
args := m.Called(ctx, uuid, orgID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestModule_Create(t *testing.T) {
|
||||
mockStore := new(MockStore)
|
||||
module := impltracefunnel.NewModule(mockStore)
|
||||
|
||||
ctx := context.Background()
|
||||
timestamp := time.Now().UnixMilli()
|
||||
name := "test-funnel"
|
||||
userID := valuer.GenerateUUID()
|
||||
orgID := valuer.GenerateUUID()
|
||||
|
||||
mockStore.On("Create", ctx, mock.MatchedBy(func(f *traceFunnels.StorableFunnel) bool {
|
||||
return f.Name == name &&
|
||||
f.CreatedBy == userID.String() &&
|
||||
f.OrgID == orgID &&
|
||||
f.CreatedByUser != nil &&
|
||||
f.CreatedByUser.ID == userID &&
|
||||
f.CreatedAt.UnixNano()/1000000 == timestamp
|
||||
})).Return(nil)
|
||||
|
||||
funnel, err := module.Create(ctx, timestamp, name, userID, orgID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, funnel)
|
||||
assert.Equal(t, name, funnel.Name)
|
||||
assert.Equal(t, userID.String(), funnel.CreatedBy)
|
||||
assert.Equal(t, orgID, funnel.OrgID)
|
||||
assert.NotNil(t, funnel.CreatedByUser)
|
||||
assert.Equal(t, userID, funnel.CreatedByUser.ID)
|
||||
|
||||
mockStore.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestModule_Get(t *testing.T) {
|
||||
mockStore := new(MockStore)
|
||||
module := impltracefunnel.NewModule(mockStore)
|
||||
|
||||
ctx := context.Background()
|
||||
funnelID := valuer.GenerateUUID()
|
||||
orgID := valuer.GenerateUUID()
|
||||
expectedFunnel := &traceFunnels.StorableFunnel{
|
||||
Name: "test-funnel",
|
||||
}
|
||||
|
||||
mockStore.On("Get", ctx, funnelID, orgID).Return(expectedFunnel, nil)
|
||||
|
||||
funnel, err := module.Get(ctx, funnelID, orgID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedFunnel, funnel)
|
||||
|
||||
mockStore.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestModule_Update(t *testing.T) {
|
||||
mockStore := new(MockStore)
|
||||
module := impltracefunnel.NewModule(mockStore)
|
||||
|
||||
ctx := context.Background()
|
||||
userID := valuer.GenerateUUID()
|
||||
funnel := &traceFunnels.StorableFunnel{
|
||||
Name: "test-funnel",
|
||||
}
|
||||
|
||||
mockStore.On("Update", ctx, funnel).Return(nil)
|
||||
|
||||
err := module.Update(ctx, funnel, userID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, userID.String(), funnel.UpdatedBy)
|
||||
|
||||
mockStore.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestModule_List(t *testing.T) {
|
||||
mockStore := new(MockStore)
|
||||
module := impltracefunnel.NewModule(mockStore)
|
||||
|
||||
ctx := context.Background()
|
||||
orgID := valuer.GenerateUUID()
|
||||
expectedFunnels := []*traceFunnels.StorableFunnel{
|
||||
{
|
||||
Name: "funnel-1",
|
||||
OrgID: orgID,
|
||||
},
|
||||
{
|
||||
Name: "funnel-2",
|
||||
OrgID: orgID,
|
||||
},
|
||||
}
|
||||
|
||||
mockStore.On("List", ctx, orgID).Return(expectedFunnels, nil)
|
||||
|
||||
funnels, err := module.List(ctx, orgID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, funnels, 2)
|
||||
assert.Equal(t, expectedFunnels, funnels)
|
||||
|
||||
mockStore.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestModule_Delete(t *testing.T) {
|
||||
mockStore := new(MockStore)
|
||||
module := impltracefunnel.NewModule(mockStore)
|
||||
|
||||
ctx := context.Background()
|
||||
funnelID := valuer.GenerateUUID()
|
||||
orgID := valuer.GenerateUUID()
|
||||
|
||||
mockStore.On("Delete", ctx, funnelID, orgID).Return(nil)
|
||||
|
||||
err := module.Delete(ctx, funnelID, orgID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockStore.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestModule_GetFunnelMetadata(t *testing.T) {
|
||||
mockStore := new(MockStore)
|
||||
module := impltracefunnel.NewModule(mockStore)
|
||||
|
||||
ctx := context.Background()
|
||||
funnelID := valuer.GenerateUUID()
|
||||
orgID := valuer.GenerateUUID()
|
||||
now := time.Now()
|
||||
expectedFunnel := &traceFunnels.StorableFunnel{
|
||||
Description: "test description",
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
}
|
||||
|
||||
mockStore.On("Get", ctx, funnelID, orgID).Return(expectedFunnel, nil)
|
||||
|
||||
createdAt, updatedAt, description, err := module.GetFunnelMetadata(ctx, funnelID, orgID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, now.UnixNano()/1000000, createdAt)
|
||||
assert.Equal(t, now.UnixNano()/1000000, updatedAt)
|
||||
assert.Equal(t, "test description", description)
|
||||
|
||||
mockStore.AssertExpectations(t)
|
||||
}
|
@ -11,8 +11,10 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"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/user"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
@ -22,20 +24,22 @@ import (
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
store types.UserStore
|
||||
jwt *authtypes.JWT
|
||||
emailing emailing.Emailing
|
||||
settings factory.ScopedProviderSettings
|
||||
store types.UserStore
|
||||
jwt *authtypes.JWT
|
||||
emailing emailing.Emailing
|
||||
settings factory.ScopedProviderSettings
|
||||
orgSetter organization.Setter
|
||||
}
|
||||
|
||||
// This module is a WIP, don't take inspiration from this.
|
||||
func NewModule(store types.UserStore, jwt *authtypes.JWT, emailing emailing.Emailing, providerSettings factory.ProviderSettings) user.Module {
|
||||
func NewModule(store types.UserStore, jwt *authtypes.JWT, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter) user.Module {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
|
||||
return &Module{
|
||||
store: store,
|
||||
jwt: jwt,
|
||||
emailing: emailing,
|
||||
settings: settings,
|
||||
store: store,
|
||||
jwt: jwt,
|
||||
emailing: emailing,
|
||||
settings: settings,
|
||||
orgSetter: orgSetter,
|
||||
}
|
||||
}
|
||||
|
||||
@ -538,3 +542,36 @@ func (m *Module) ListDomains(ctx context.Context, orgID valuer.UUID) ([]*types.G
|
||||
func (m *Module) UpdateDomain(ctx context.Context, domain *types.GettableOrgDomain) error {
|
||||
return m.store.UpdateDomain(ctx, domain)
|
||||
}
|
||||
|
||||
func (m *Module) Register(ctx context.Context, req *types.PostableRegisterOrgAndAdmin) (*types.User, error) {
|
||||
if req.Email == "" {
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "email is required")
|
||||
}
|
||||
|
||||
if req.Password == "" {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "password is required")
|
||||
}
|
||||
|
||||
organization := types.NewOrganization(req.OrgDisplayName)
|
||||
err := m.orgSetter.Create(ctx, organization)
|
||||
if err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
user, err := types.NewUser(req.Name, req.Email, types.RoleAdmin.String(), organization.ID.StringValue())
|
||||
if err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
password, err := types.NewFactorPassword(req.Password)
|
||||
if err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
user, err = m.CreateUserWithPassword(ctx, user, password)
|
||||
if err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
@ -62,6 +62,9 @@ type Module interface {
|
||||
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error)
|
||||
RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UUID) error
|
||||
GetAPIKey(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.StorableAPIKeyUser, error)
|
||||
|
||||
// Register
|
||||
Register(ctx context.Context, req *types.PostableRegisterOrgAndAdmin) (*types.User, error)
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
|
@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
promValue "github.com/prometheus/prometheus/model/value"
|
||||
"github.com/prometheus/prometheus/prompb"
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/remote"
|
||||
@ -188,9 +190,10 @@ func (client *client) querySamples(ctx context.Context, start int64, end int64,
|
||||
var fingerprint, prevFingerprint uint64
|
||||
var timestampMs int64
|
||||
var value float64
|
||||
var flags uint32
|
||||
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&metricName, &fingerprint, ×tampMs, &value); err != nil {
|
||||
if err := rows.Scan(&metricName, &fingerprint, ×tampMs, &value, &flags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -208,6 +211,10 @@ func (client *client) querySamples(ctx context.Context, start int64, end int64,
|
||||
}
|
||||
}
|
||||
|
||||
if flags&1 == 1 {
|
||||
value = math.Float64frombits(promValue.StaleNaN)
|
||||
}
|
||||
|
||||
// add samples to current time series
|
||||
ts.Samples = append(ts.Samples, prompb.Sample{
|
||||
Timestamp: timestampMs,
|
||||
|
@ -6267,9 +6267,6 @@ func (r *ClickHouseReader) GetUpdatedMetricsMetadata(ctx context.Context, orgID
|
||||
if err == nil {
|
||||
cachedMetadata[metricName] = metadata
|
||||
} else {
|
||||
if err != nil {
|
||||
zap.L().Error("Error retrieving metrics metadata from cache", zap.String("metric_name", metricName), zap.Error(err))
|
||||
}
|
||||
missingMetrics = append(missingMetrics, metricName)
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
@ -32,9 +34,7 @@ type Controller struct {
|
||||
serviceConfigRepo ServiceConfigDatabase
|
||||
}
|
||||
|
||||
func NewController(sqlStore sqlstore.SQLStore) (
|
||||
*Controller, error,
|
||||
) {
|
||||
func NewController(sqlStore sqlstore.SQLStore) (*Controller, error) {
|
||||
accountsRepo, err := newCloudProviderAccountsRepository(sqlStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create cloud provider accounts repo: %w", err)
|
||||
@ -55,9 +55,7 @@ type ConnectedAccountsListResponse struct {
|
||||
Accounts []types.Account `json:"accounts"`
|
||||
}
|
||||
|
||||
func (c *Controller) ListConnectedAccounts(
|
||||
ctx context.Context, orgId string, cloudProvider string,
|
||||
) (
|
||||
func (c *Controller) ListConnectedAccounts(ctx context.Context, orgId string, cloudProvider string) (
|
||||
*ConnectedAccountsListResponse, *model.ApiError,
|
||||
) {
|
||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||
@ -103,9 +101,7 @@ type GenerateConnectionUrlResponse struct {
|
||||
ConnectionUrl string `json:"connection_url"`
|
||||
}
|
||||
|
||||
func (c *Controller) GenerateConnectionUrl(
|
||||
ctx context.Context, orgId string, cloudProvider string, req GenerateConnectionUrlRequest,
|
||||
) (*GenerateConnectionUrlResponse, *model.ApiError) {
|
||||
func (c *Controller) GenerateConnectionUrl(ctx context.Context, orgId string, cloudProvider string, req GenerateConnectionUrlRequest) (*GenerateConnectionUrlResponse, *model.ApiError) {
|
||||
// Account connection with a simple connection URL may not be available for all providers.
|
||||
if cloudProvider != "aws" {
|
||||
return nil, model.BadRequest(fmt.Errorf("unsupported cloud provider: %s", cloudProvider))
|
||||
@ -154,9 +150,7 @@ type AccountStatusResponse struct {
|
||||
Status types.AccountStatus `json:"status"`
|
||||
}
|
||||
|
||||
func (c *Controller) GetAccountStatus(
|
||||
ctx context.Context, orgId string, cloudProvider string, accountId string,
|
||||
) (
|
||||
func (c *Controller) GetAccountStatus(ctx context.Context, orgId string, cloudProvider string, accountId string) (
|
||||
*AccountStatusResponse, *model.ApiError,
|
||||
) {
|
||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||
@ -198,9 +192,7 @@ type IntegrationConfigForAgent struct {
|
||||
TelemetryCollectionStrategy *CompiledCollectionStrategy `json:"telemetry,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Controller) CheckInAsAgent(
|
||||
ctx context.Context, orgId string, cloudProvider string, req AgentCheckInRequest,
|
||||
) (*AgentCheckInResponse, error) {
|
||||
func (c *Controller) CheckInAsAgent(ctx context.Context, orgId string, cloudProvider string, req AgentCheckInRequest) (*AgentCheckInResponse, error) {
|
||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
@ -293,13 +285,7 @@ type UpdateAccountConfigRequest struct {
|
||||
Config types.AccountConfig `json:"config"`
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateAccountConfig(
|
||||
ctx context.Context,
|
||||
orgId string,
|
||||
cloudProvider string,
|
||||
accountId string,
|
||||
req UpdateAccountConfigRequest,
|
||||
) (*types.Account, *model.ApiError) {
|
||||
func (c *Controller) UpdateAccountConfig(ctx context.Context, orgId string, cloudProvider string, accountId string, req UpdateAccountConfigRequest) (*types.Account, *model.ApiError) {
|
||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
@ -316,9 +302,7 @@ func (c *Controller) UpdateAccountConfig(
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func (c *Controller) DisconnectAccount(
|
||||
ctx context.Context, orgId string, cloudProvider string, accountId string,
|
||||
) (*types.CloudIntegration, *model.ApiError) {
|
||||
func (c *Controller) DisconnectAccount(ctx context.Context, orgId string, cloudProvider string, accountId string) (*types.CloudIntegration, *model.ApiError) {
|
||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
@ -520,10 +504,8 @@ func (c *Controller) UpdateServiceConfig(
|
||||
|
||||
// All dashboards that are available based on cloud integrations configuration
|
||||
// across all cloud providers
|
||||
func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) (
|
||||
[]*types.Dashboard, *model.ApiError,
|
||||
) {
|
||||
allDashboards := []*types.Dashboard{}
|
||||
func (c *Controller) AvailableDashboards(ctx context.Context, orgId valuer.UUID) ([]*dashboardtypes.Dashboard, *model.ApiError) {
|
||||
allDashboards := []*dashboardtypes.Dashboard{}
|
||||
|
||||
for _, provider := range []string{"aws"} {
|
||||
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, provider)
|
||||
@ -539,11 +521,8 @@ func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) (
|
||||
return allDashboards, nil
|
||||
}
|
||||
|
||||
func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||
ctx context.Context, orgID string, cloudProvider string,
|
||||
) ([]*types.Dashboard, *model.ApiError) {
|
||||
|
||||
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgID, cloudProvider)
|
||||
func (c *Controller) AvailableDashboardsForCloudProvider(ctx context.Context, orgID valuer.UUID, cloudProvider string) ([]*dashboardtypes.Dashboard, *model.ApiError) {
|
||||
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgID.StringValue(), cloudProvider)
|
||||
if apiErr != nil {
|
||||
return nil, model.WrapApiError(apiErr, "couldn't list connected cloud accounts")
|
||||
}
|
||||
@ -554,7 +533,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||
for _, ar := range accountRecords {
|
||||
if ar.AccountID != nil {
|
||||
configsBySvcId, apiErr := c.serviceConfigRepo.getAllForAccount(
|
||||
ctx, orgID, ar.ID.StringValue(),
|
||||
ctx, orgID.StringValue(), ar.ID.StringValue(),
|
||||
)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
@ -573,16 +552,15 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
svcDashboards := []*types.Dashboard{}
|
||||
svcDashboards := []*dashboardtypes.Dashboard{}
|
||||
for _, svc := range allServices {
|
||||
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
|
||||
if serviceDashboardsCreatedAt != nil {
|
||||
for _, d := range svc.Assets.Dashboards {
|
||||
isLocked := 1
|
||||
author := fmt.Sprintf("%s-integration", cloudProvider)
|
||||
svcDashboards = append(svcDashboards, &types.Dashboard{
|
||||
UUID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
||||
Locked: &isLocked,
|
||||
svcDashboards = append(svcDashboards, &dashboardtypes.Dashboard{
|
||||
ID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
||||
Locked: true,
|
||||
Data: *d.Definition,
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: *serviceDashboardsCreatedAt,
|
||||
@ -592,6 +570,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||
CreatedBy: author,
|
||||
UpdatedBy: author,
|
||||
},
|
||||
OrgID: orgID,
|
||||
})
|
||||
}
|
||||
servicesWithAvailableMetrics[svc.Id] = nil
|
||||
@ -600,11 +579,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||
|
||||
return svcDashboards, nil
|
||||
}
|
||||
func (c *Controller) GetDashboardById(
|
||||
ctx context.Context,
|
||||
orgId string,
|
||||
dashboardUuid string,
|
||||
) (*types.Dashboard, *model.ApiError) {
|
||||
func (c *Controller) GetDashboardById(ctx context.Context, orgId valuer.UUID, dashboardUuid string) (*dashboardtypes.Dashboard, *model.ApiError) {
|
||||
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
@ -612,38 +587,28 @@ func (c *Controller) GetDashboardById(
|
||||
|
||||
allDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, cloudProvider)
|
||||
if apiErr != nil {
|
||||
return nil, model.WrapApiError(
|
||||
apiErr, fmt.Sprintf("couldn't list available dashboards"),
|
||||
)
|
||||
return nil, model.WrapApiError(apiErr, "couldn't list available dashboards")
|
||||
}
|
||||
|
||||
for _, d := range allDashboards {
|
||||
if d.UUID == dashboardUuid {
|
||||
if d.ID == dashboardUuid {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, model.NotFoundError(fmt.Errorf(
|
||||
"couldn't find dashboard with uuid: %s", dashboardUuid,
|
||||
))
|
||||
return nil, model.NotFoundError(fmt.Errorf("couldn't find dashboard with uuid: %s", dashboardUuid))
|
||||
}
|
||||
|
||||
func (c *Controller) dashboardUuid(
|
||||
cloudProvider string, svcId string, dashboardId string,
|
||||
) string {
|
||||
return fmt.Sprintf(
|
||||
"cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId,
|
||||
)
|
||||
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId)
|
||||
}
|
||||
|
||||
func (c *Controller) parseDashboardUuid(dashboardUuid string) (
|
||||
cloudProvider string, svcId string, dashboardId string, apiErr *model.ApiError,
|
||||
) {
|
||||
func (c *Controller) parseDashboardUuid(dashboardUuid string) (cloudProvider string, svcId string, dashboardId string, apiErr *model.ApiError) {
|
||||
parts := strings.SplitN(dashboardUuid, "--", 4)
|
||||
if len(parts) != 4 || parts[0] != "cloud-integration" {
|
||||
return "", "", "", model.BadRequest(fmt.Errorf(
|
||||
"invalid cloud integration dashboard id",
|
||||
))
|
||||
return "", "", "", model.BadRequest(fmt.Errorf("invalid cloud integration dashboard id"))
|
||||
}
|
||||
|
||||
return parts[1], parts[2], parts[3], nil
|
||||
|
@ -3,17 +3,23 @@ package cloudintegrations
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/emailing/noopemailing"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/emailing/emailingtest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
"github.com/SigNoz/signoz/pkg/sharder"
|
||||
"github.com/SigNoz/signoz/pkg/sharder/noopsharder"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -24,11 +30,16 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
sharder, err := noopsharder.New(context.TODO(), providerSettings, sharder.Config{})
|
||||
require.NoError(err)
|
||||
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlStore), sharder)
|
||||
alertmanager, err := signozalertmanager.New(context.TODO(), providerSettings, alertmanager.Config{Provider: "signoz", Signoz: alertmanager.Signoz{PollInterval: 10 * time.Second, Config: alertmanagerserver.NewConfig()}}, sqlStore, orgGetter)
|
||||
require.NoError(err)
|
||||
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
|
||||
emailing := emailingtest.New()
|
||||
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager)
|
||||
user, apiErr := createTestUser(modules.OrgSetter, modules.User)
|
||||
require.Nil(apiErr)
|
||||
|
||||
// should be able to generate connection url for
|
||||
@ -74,11 +85,17 @@ func TestAgentCheckIns(t *testing.T) {
|
||||
sqlStore := utils.NewQueryServiceDBForTests(t)
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
sharder, err := noopsharder.New(context.TODO(), providerSettings, sharder.Config{})
|
||||
require.NoError(err)
|
||||
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlStore), sharder)
|
||||
alertmanager, err := signozalertmanager.New(context.TODO(), providerSettings, alertmanager.Config{Provider: "signoz", Signoz: alertmanager.Signoz{PollInterval: 10 * time.Second, Config: alertmanagerserver.NewConfig()}}, sqlStore, orgGetter)
|
||||
require.NoError(err)
|
||||
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
|
||||
emailing := emailingtest.New()
|
||||
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager)
|
||||
user, apiErr := createTestUser(modules.OrgSetter, modules.User)
|
||||
require.Nil(apiErr)
|
||||
|
||||
// An agent should be able to check in from a cloud account even
|
||||
@ -164,11 +181,16 @@ func TestCantDisconnectNonExistentAccount(t *testing.T) {
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
sharder, err := noopsharder.New(context.TODO(), providerSettings, sharder.Config{})
|
||||
require.NoError(err)
|
||||
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlStore), sharder)
|
||||
alertmanager, err := signozalertmanager.New(context.TODO(), providerSettings, alertmanager.Config{Provider: "signoz", Signoz: alertmanager.Signoz{PollInterval: 10 * time.Second, Config: alertmanagerserver.NewConfig()}}, sqlStore, orgGetter)
|
||||
require.NoError(err)
|
||||
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
|
||||
emailing := emailingtest.New()
|
||||
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager)
|
||||
user, apiErr := createTestUser(modules.OrgSetter, modules.User)
|
||||
require.Nil(apiErr)
|
||||
|
||||
// Attempting to disconnect a non-existent account should return error
|
||||
@ -186,11 +208,16 @@ func TestConfigureService(t *testing.T) {
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
sharder, err := noopsharder.New(context.TODO(), providerSettings, sharder.Config{})
|
||||
require.NoError(err)
|
||||
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlStore), sharder)
|
||||
alertmanager, err := signozalertmanager.New(context.TODO(), providerSettings, alertmanager.Config{Provider: "signoz", Signoz: alertmanager.Signoz{PollInterval: 10 * time.Second, Config: alertmanagerserver.NewConfig()}}, sqlStore, orgGetter)
|
||||
require.NoError(err)
|
||||
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
|
||||
emailing := emailingtest.New()
|
||||
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager)
|
||||
user, apiErr := createTestUser(modules.OrgSetter, modules.User)
|
||||
require.Nil(apiErr)
|
||||
|
||||
// create a connected account
|
||||
@ -305,7 +332,7 @@ func makeTestConnectedAccount(t *testing.T, orgId string, controller *Controller
|
||||
return acc
|
||||
}
|
||||
|
||||
func createTestUser(organizationModule organization.Module, userModule user.Module) (*types.User, *model.ApiError) {
|
||||
func createTestUser(organizationModule organization.Setter, userModule user.Module) (*types.User, *model.ApiError) {
|
||||
// Create a test user for auth
|
||||
ctx := context.Background()
|
||||
organization := types.NewOrganization("test")
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user