mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-17 06:45:59 +08:00
Merge branch 'main' into chore/trace-funnels-bugfixes/improvements
This commit is contained in:
commit
4db9846fd4
@ -3,9 +3,10 @@ package httplicensing
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
@ -177,6 +178,11 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = provider.InitFeatures(ctx, activeLicense.Features)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,13 +96,7 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
// note: add ee override methods first
|
// note: add ee override methods first
|
||||||
|
|
||||||
// routes available only in ee version
|
// routes available only in ee version
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
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
|
// paid plans specific routes
|
||||||
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost)
|
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/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/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
|
// v3
|
||||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Activate)).Methods(http.MethodPost)
|
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)
|
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")
|
|
||||||
}
|
|
@ -298,6 +298,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
|||||||
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
||||||
apiHandler.RegisterThirdPartyApiRoutes(r, am)
|
apiHandler.RegisterThirdPartyApiRoutes(r, am)
|
||||||
apiHandler.MetricExplorerRoutes(r, am)
|
apiHandler.MetricExplorerRoutes(r, am)
|
||||||
|
apiHandler.RegisterTraceFunnelsRoutes(r, am)
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
|
@ -28,6 +28,7 @@ import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
|||||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||||
import { Route, Router, Switch } from 'react-router-dom';
|
import { Route, Router, Switch } from 'react-router-dom';
|
||||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||||
|
import { LicenseStatus } from 'types/api/licensesV3/getActive';
|
||||||
import { Userpilot } from 'userpilot';
|
import { Userpilot } from 'userpilot';
|
||||||
import { extractDomain } from 'utils/app';
|
import { extractDomain } from 'utils/app';
|
||||||
|
|
||||||
@ -171,11 +172,13 @@ function App(): JSX.Element {
|
|||||||
user &&
|
user &&
|
||||||
!!user.email
|
!!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 =
|
const isOnBasicPlan =
|
||||||
activeLicenseFetchError &&
|
(activeLicenseFetchError &&
|
||||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||||
activeLicenseFetchError?.getHttpStatusCode(),
|
activeLicenseFetchError?.getHttpStatusCode(),
|
||||||
);
|
)) ||
|
||||||
|
(activeLicense?.status && activeLicense.status === LicenseStatus.INVALID);
|
||||||
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||||
|
|
||||||
if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) {
|
if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) {
|
||||||
@ -190,6 +193,10 @@ function App(): JSX.Element {
|
|||||||
updatedRoutes = updatedRoutes.filter(
|
updatedRoutes = updatedRoutes.filter(
|
||||||
(route) => route?.path !== ROUTES.BILLING,
|
(route) => route?.path !== ROUTES.BILLING,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isEnterpriseSelfHostedUser) {
|
||||||
|
updatedRoutes.push(LIST_LICENSES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// always add support route for cloud users
|
// always add support route for cloud users
|
||||||
updatedRoutes = [...updatedRoutes, SUPPORT_ROUTE];
|
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 Overview from 'container/LogDetailedView/Overview';
|
||||||
import {
|
import {
|
||||||
aggregateAttributesResourcesToString,
|
aggregateAttributesResourcesToString,
|
||||||
|
escapeHtml,
|
||||||
removeEscapeCharacters,
|
removeEscapeCharacters,
|
||||||
unescapeString,
|
unescapeString,
|
||||||
} from 'container/LogDetailedView/utils';
|
} from 'container/LogDetailedView/utils';
|
||||||
@ -118,7 +119,7 @@ function LogDetail({
|
|||||||
const htmlBody = useMemo(
|
const htmlBody = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
__html: convert.toHtml(
|
__html: convert.toHtml(
|
||||||
dompurify.sanitize(unescapeString(log?.body || ''), {
|
dompurify.sanitize(unescapeString(escapeHtml(log?.body || '')), {
|
||||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -7,7 +7,7 @@ import cx from 'classnames';
|
|||||||
import LogDetail from 'components/LogDetail';
|
import LogDetail from 'components/LogDetail';
|
||||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
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 { FontSize } from 'container/OptionsMenu/types';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
@ -58,7 +58,7 @@ function LogGeneralField({
|
|||||||
const html = useMemo(
|
const html = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
__html: convert.toHtml(
|
__html: convert.toHtml(
|
||||||
dompurify.sanitize(unescapeString(fieldValue), {
|
dompurify.sanitize(unescapeString(escapeHtml(fieldValue)), {
|
||||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -5,7 +5,7 @@ import { DrawerProps } from 'antd';
|
|||||||
import LogDetail from 'components/LogDetail';
|
import LogDetail from 'components/LogDetail';
|
||||||
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
||||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
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 LogsExplorerContext from 'container/LogsExplorerContext';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
@ -177,7 +177,7 @@ function RawLogView({
|
|||||||
const html = useMemo(
|
const html = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
__html: convert.toHtml(
|
__html: convert.toHtml(
|
||||||
dompurify.sanitize(unescapeString(text), {
|
dompurify.sanitize(unescapeString(escapeHtml(text)), {
|
||||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Button, Typography } from 'antd';
|
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 { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||||
import useAxiosError from 'hooks/useAxiosError';
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
import { ExportPanelProps } from '.';
|
import { ExportPanelProps } from '.';
|
||||||
import {
|
import {
|
||||||
@ -33,26 +34,28 @@ function ExportPanelContainer({
|
|||||||
refetch,
|
refetch,
|
||||||
} = useGetAllDashboard();
|
} = useGetAllDashboard();
|
||||||
|
|
||||||
const handleError = useAxiosError();
|
const { showErrorModal } = useErrorModal();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutate: createNewDashboard,
|
mutate: createNewDashboard,
|
||||||
isLoading: createDashboardLoading,
|
isLoading: createDashboardLoading,
|
||||||
} = useMutation(createDashboard, {
|
} = useMutation(createDashboard, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.payload) {
|
if (data.data) {
|
||||||
onExport(data?.payload, true);
|
onExport(data?.data, true);
|
||||||
}
|
}
|
||||||
refetch();
|
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 handleExportClick = useCallback((): void => {
|
||||||
const currentSelectedDashboard = data?.find(
|
const currentSelectedDashboard = data?.data?.find(
|
||||||
({ uuid }) => uuid === selectedDashboardId,
|
({ id }) => id === selectedDashboardId,
|
||||||
);
|
);
|
||||||
|
|
||||||
onExport(currentSelectedDashboard || null, false);
|
onExport(currentSelectedDashboard || null, false);
|
||||||
@ -66,14 +69,18 @@ function ExportPanelContainer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleNewDashboard = useCallback(async () => {
|
const handleNewDashboard = useCallback(async () => {
|
||||||
createNewDashboard({
|
try {
|
||||||
title: t('new_dashboard_title', {
|
await createNewDashboard({
|
||||||
ns: 'dashboard',
|
title: t('new_dashboard_title', {
|
||||||
}),
|
ns: 'dashboard',
|
||||||
uploadedGrafana: false,
|
}),
|
||||||
version: ENTITY_VERSION_V4,
|
uploadedGrafana: false,
|
||||||
});
|
version: ENTITY_VERSION_V4,
|
||||||
}, [t, createNewDashboard]);
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showErrorModal(error as APIError);
|
||||||
|
}
|
||||||
|
}, [createNewDashboard, t, showErrorModal]);
|
||||||
|
|
||||||
const isDashboardLoading = isAllDashboardsLoading || createDashboardLoading;
|
const isDashboardLoading = isAllDashboardsLoading || createDashboardLoading;
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { SelectProps } from 'antd';
|
import { SelectProps } from 'antd';
|
||||||
import { PayloadProps as AllDashboardsData } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
export const getSelectOptions = (
|
export const getSelectOptions = (data: Dashboard[]): SelectProps['options'] =>
|
||||||
data: AllDashboardsData,
|
data.map(({ id, data }) => ({
|
||||||
): SelectProps['options'] =>
|
|
||||||
data.map(({ uuid, data }) => ({
|
|
||||||
label: data.title,
|
label: data.title,
|
||||||
value: uuid,
|
value: id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const filterOptions: SelectProps['filterOption'] = (
|
export const filterOptions: SelectProps['filterOption'] = (
|
||||||
|
@ -38,7 +38,7 @@ export default function DashboardEmptyState(): JSX.Element {
|
|||||||
setSelectedRowWidgetId(null);
|
setSelectedRowWidgetId(null);
|
||||||
handleToggleDashboardSlider(true);
|
handleToggleDashboardSlider(true);
|
||||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||||
dashboardId: selectedDashboard?.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
dashboardName: selectedDashboard?.data.title,
|
dashboardName: selectedDashboard?.data.title,
|
||||||
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { AppProvider } from 'providers/App/App';
|
import { AppProvider } from 'providers/App/App';
|
||||||
|
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
@ -189,24 +190,26 @@ describe('WidgetGraphComponent', () => {
|
|||||||
it('should show correct menu items when hovering over more options while loading', async () => {
|
it('should show correct menu items when hovering over more options while loading', async () => {
|
||||||
const { getByTestId, findByRole, getByText, container } = render(
|
const { getByTestId, findByRole, getByText, container } = render(
|
||||||
<MockQueryClientProvider>
|
<MockQueryClientProvider>
|
||||||
<Provider store={store}>
|
<ErrorModalProvider>
|
||||||
<AppProvider>
|
<Provider store={store}>
|
||||||
<WidgetGraphComponent
|
<AppProvider>
|
||||||
widget={mockProps.widget}
|
<WidgetGraphComponent
|
||||||
queryResponse={mockProps.queryResponse}
|
widget={mockProps.widget}
|
||||||
errorMessage={mockProps.errorMessage}
|
queryResponse={mockProps.queryResponse}
|
||||||
version={mockProps.version}
|
errorMessage={mockProps.errorMessage}
|
||||||
headerMenuList={mockProps.headerMenuList}
|
version={mockProps.version}
|
||||||
isWarning={mockProps.isWarning}
|
headerMenuList={mockProps.headerMenuList}
|
||||||
isFetchingResponse={mockProps.isFetchingResponse}
|
isWarning={mockProps.isWarning}
|
||||||
setRequestData={mockProps.setRequestData}
|
isFetchingResponse={mockProps.isFetchingResponse}
|
||||||
onClickHandler={mockProps.onClickHandler}
|
setRequestData={mockProps.setRequestData}
|
||||||
onDragSelect={mockProps.onDragSelect}
|
onClickHandler={mockProps.onClickHandler}
|
||||||
openTracesButton={mockProps.openTracesButton}
|
onDragSelect={mockProps.onDragSelect}
|
||||||
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
|
openTracesButton={mockProps.openTracesButton}
|
||||||
/>
|
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
|
||||||
</AppProvider>
|
/>
|
||||||
</Provider>
|
</AppProvider>
|
||||||
|
</Provider>
|
||||||
|
</ErrorModalProvider>
|
||||||
</MockQueryClientProvider>,
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import { Skeleton, Tooltip, Typography } from 'antd';
|
|||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||||
import { ToggleGraphProps } from 'components/Graph/types';
|
import { ToggleGraphProps } from 'components/Graph/types';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
||||||
@ -31,7 +30,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
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 { DataSource } from 'types/common/queryBuilder';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
@ -119,29 +118,23 @@ function WidgetGraphComponent({
|
|||||||
const updatedLayout =
|
const updatedLayout =
|
||||||
selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || [];
|
selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || [];
|
||||||
|
|
||||||
const updatedSelectedDashboard: Dashboard = {
|
const updatedSelectedDashboard: Props = {
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
widgets: updatedWidgets,
|
widgets: updatedWidgets,
|
||||||
layout: updatedLayout,
|
layout: updatedLayout,
|
||||||
},
|
},
|
||||||
uuid: selectedDashboard.uuid,
|
id: selectedDashboard.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
if (setSelectedDashboard && updatedDashboard.data) {
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
}
|
}
|
||||||
setDeleteModal(false);
|
setDeleteModal(false);
|
||||||
},
|
},
|
||||||
onError: () => {
|
|
||||||
notifications.error({
|
|
||||||
message: SOMETHING_WENT_WRONG,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -166,7 +159,8 @@ function WidgetGraphComponent({
|
|||||||
|
|
||||||
updateDashboardMutation.mutateAsync(
|
updateDashboardMutation.mutateAsync(
|
||||||
{
|
{
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
layout,
|
layout,
|
||||||
@ -183,9 +177,9 @@ function WidgetGraphComponent({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
if (setSelectedDashboard && updatedDashboard.data) {
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
}
|
}
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: 'Panel cloned successfully, redirecting to new copy.',
|
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 { useForm } from 'antd/es/form/Form';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { themeColors } from 'constants/theme';
|
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 { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { defaultTo, isUndefined } from 'lodash-es';
|
import { defaultTo, isUndefined } from 'lodash-es';
|
||||||
@ -36,7 +34,8 @@ import { ItemCallback, Layout } from 'react-grid-layout';
|
|||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { UpdateTimeInterval } from 'store/actions';
|
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 { ROLES, USER_ROLES } from 'types/roles';
|
||||||
import { ComponentTypes } from 'utils/permission';
|
import { ComponentTypes } from 'utils/permission';
|
||||||
|
|
||||||
@ -107,7 +106,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
|||||||
|
|
||||||
const updateDashboardMutation = useUpdateDashboard();
|
const updateDashboardMutation = useUpdateDashboard();
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
|
|
||||||
let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
|
let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
|
||||||
@ -158,20 +156,20 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!logEventCalledRef.current && !isUndefined(data)) {
|
if (!logEventCalledRef.current && !isUndefined(data)) {
|
||||||
logEvent('Dashboard Detail: Opened', {
|
logEvent('Dashboard Detail: Opened', {
|
||||||
dashboardId: data.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
dashboardName: data.title,
|
dashboardName: data.title,
|
||||||
numberOfPanels: data.widgets?.length,
|
numberOfPanels: data.widgets?.length,
|
||||||
numberOfVariables: Object.keys(data?.variables || {}).length || 0,
|
numberOfVariables: Object.keys(data?.variables || {}).length || 0,
|
||||||
});
|
});
|
||||||
logEventCalledRef.current = true;
|
logEventCalledRef.current = true;
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data, selectedDashboard?.id]);
|
||||||
|
|
||||||
const onSaveHandler = (): void => {
|
const onSaveHandler = (): void => {
|
||||||
if (!selectedDashboard) return;
|
if (!selectedDashboard) return;
|
||||||
|
|
||||||
const updatedDashboard: Dashboard = {
|
const updatedDashboard: Props = {
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
panelMap: { ...currentPanelMap },
|
panelMap: { ...currentPanelMap },
|
||||||
@ -186,24 +184,18 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
|||||||
return widget;
|
return widget;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDashboardMutation.mutate(updatedDashboard, {
|
updateDashboardMutation.mutate(updatedDashboard, {
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
setSelectedRowWidgetId(null);
|
setSelectedRowWidgetId(null);
|
||||||
if (updatedDashboard.payload) {
|
if (updatedDashboard.data) {
|
||||||
if (updatedDashboard.payload.data.layout)
|
if (updatedDashboard.data.data.layout)
|
||||||
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
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);
|
updatedWidgets?.push(currentWidget);
|
||||||
|
|
||||||
const updatedSelectedDashboard: Dashboard = {
|
const updatedSelectedDashboard: Props = {
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
widgets: updatedWidgets,
|
widgets: updatedWidgets,
|
||||||
},
|
},
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
if (setSelectedDashboard && updatedDashboard.data) {
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
}
|
}
|
||||||
if (setPanelMap)
|
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
|
||||||
form.setFieldValue('title', '');
|
form.setFieldValue('title', '');
|
||||||
setIsSettingsModalOpen(false);
|
setIsSettingsModalOpen(false);
|
||||||
setCurrentSelectRowId(null);
|
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 };
|
const updatedPanelMap = { ...currentPanelMap };
|
||||||
delete updatedPanelMap[currentSelectRowId];
|
delete updatedPanelMap[currentSelectRowId];
|
||||||
|
|
||||||
const updatedSelectedDashboard: Dashboard = {
|
const updatedSelectedDashboard: Props = {
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
widgets: updatedWidgets,
|
widgets: updatedWidgets,
|
||||||
layout: updatedLayout,
|
layout: updatedLayout,
|
||||||
panelMap: updatedPanelMap,
|
panelMap: updatedPanelMap,
|
||||||
},
|
},
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
if (setSelectedDashboard && updatedDashboard.data) {
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
}
|
}
|
||||||
if (setPanelMap)
|
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
setCurrentSelectRowId(null);
|
setCurrentSelectRowId(null);
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
||||||
onError: () => {
|
|
||||||
notifications.error({
|
|
||||||
message: SOMETHING_WENT_WRONG,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const isDashboardEmpty = useMemo(
|
const isDashboardEmpty = useMemo(
|
||||||
|
@ -33,7 +33,7 @@ export default function Dashboards({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dashboardsList) return;
|
if (!dashboardsList) return;
|
||||||
|
|
||||||
const sortedDashboards = dashboardsList.sort((a, b) => {
|
const sortedDashboards = dashboardsList.data.sort((a, b) => {
|
||||||
const aUpdateAt = new Date(a.updatedAt).getTime();
|
const aUpdateAt = new Date(a.updatedAt).getTime();
|
||||||
const bUpdateAt = new Date(b.updatedAt).getTime();
|
const bUpdateAt = new Date(b.updatedAt).getTime();
|
||||||
return bUpdateAt - aUpdateAt;
|
return bUpdateAt - aUpdateAt;
|
||||||
@ -103,7 +103,7 @@ export default function Dashboards({
|
|||||||
<div className="home-dashboards-list-container home-data-item-container">
|
<div className="home-dashboards-list-container home-data-item-container">
|
||||||
<div className="dashboards-list">
|
<div className="dashboards-list">
|
||||||
{sortedDashboards.slice(0, 5).map((dashboard) => {
|
{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 => {
|
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -134,7 +134,7 @@ export default function Dashboards({
|
|||||||
<div className="dashboard-item-name-container home-data-item-name-container">
|
<div className="dashboard-item-name-container home-data-item-name-container">
|
||||||
<img
|
<img
|
||||||
src={
|
src={
|
||||||
dashboard.id % 2 === 0
|
Math.random() % 2 === 0
|
||||||
? '/Icons/eight-ball.svg'
|
? '/Icons/eight-ball.svg'
|
||||||
: '/Icons/circus-tent.svg'
|
: '/Icons/circus-tent.svg'
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { TableProps } from 'antd/lib';
|
import { TableProps } from 'antd/lib';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import createDashboard from 'api/dashboard/create';
|
import createDashboard from 'api/v1/dashboards/create';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
@ -63,6 +63,7 @@ import {
|
|||||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||||
import { useAppContext } from 'providers/App/App';
|
import { useAppContext } from 'providers/App/App';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
import { useTimezone } from 'providers/Timezone';
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
@ -83,6 +84,7 @@ import {
|
|||||||
WidgetRow,
|
WidgetRow,
|
||||||
Widgets,
|
Widgets,
|
||||||
} from 'types/api/dashboard/getAll';
|
} from 'types/api/dashboard/getAll';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
|
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
|
||||||
import ImportJSON from './ImportJSON';
|
import ImportJSON from './ImportJSON';
|
||||||
@ -226,7 +228,7 @@ function DashboardsList(): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const filteredDashboards = filterDashboard(
|
const filteredDashboards = filterDashboard(
|
||||||
searchString,
|
searchString,
|
||||||
dashboardListResponse || [],
|
dashboardListResponse?.data || [],
|
||||||
);
|
);
|
||||||
if (sortOrder.columnKey === 'updatedAt') {
|
if (sortOrder.columnKey === 'updatedAt') {
|
||||||
sortDashboardsByUpdatedAt(filteredDashboards || []);
|
sortDashboardsByUpdatedAt(filteredDashboards || []);
|
||||||
@ -256,17 +258,19 @@ function DashboardsList(): JSX.Element {
|
|||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { showErrorModal } = useErrorModal();
|
||||||
|
|
||||||
const data: Data[] =
|
const data: Data[] =
|
||||||
dashboards?.map((e) => ({
|
dashboards?.map((e) => ({
|
||||||
createdAt: e.createdAt,
|
createdAt: e.createdAt,
|
||||||
description: e.data.description || '',
|
description: e.data.description || '',
|
||||||
id: e.uuid,
|
id: e.id,
|
||||||
lastUpdatedTime: e.updatedAt,
|
lastUpdatedTime: e.updatedAt,
|
||||||
name: e.data.title,
|
name: e.data.title,
|
||||||
tags: e.data.tags || [],
|
tags: e.data.tags || [],
|
||||||
key: e.uuid,
|
key: e.id,
|
||||||
createdBy: e.createdBy,
|
createdBy: e.createdBy,
|
||||||
isLocked: !!e.isLocked || false,
|
isLocked: !!e.locked || false,
|
||||||
lastUpdatedBy: e.updatedBy,
|
lastUpdatedBy: e.updatedBy,
|
||||||
image: e.data.image || Base64Icons[0],
|
image: e.data.image || Base64Icons[0],
|
||||||
variables: e.data.variables,
|
variables: e.data.variables,
|
||||||
@ -292,28 +296,20 @@ function DashboardsList(): JSX.Element {
|
|||||||
version: ENTITY_VERSION_V4,
|
version: ENTITY_VERSION_V4,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
safeNavigate(
|
||||||
safeNavigate(
|
generatePath(ROUTES.DASHBOARD, {
|
||||||
generatePath(ROUTES.DASHBOARD, {
|
dashboardId: response.data.id,
|
||||||
dashboardId: response.payload.uuid,
|
}),
|
||||||
}),
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setNewDashboardState({
|
|
||||||
...newDashboardState,
|
|
||||||
loading: false,
|
|
||||||
error: true,
|
|
||||||
errorMessage: response.error || 'Something went wrong',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
showErrorModal(error as APIError);
|
||||||
setNewDashboardState({
|
setNewDashboardState({
|
||||||
...newDashboardState,
|
...newDashboardState,
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
|
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [newDashboardState, safeNavigate, t]);
|
}, [newDashboardState, safeNavigate, showErrorModal, t]);
|
||||||
|
|
||||||
const onModalHandler = (uploadedGrafana: boolean): void => {
|
const onModalHandler = (uploadedGrafana: boolean): void => {
|
||||||
logEvent('Dashboard List: Import JSON clicked', {});
|
logEvent('Dashboard List: Import JSON clicked', {});
|
||||||
@ -327,7 +323,7 @@ function DashboardsList(): JSX.Element {
|
|||||||
const searchText = (event as React.BaseSyntheticEvent)?.target?.value || '';
|
const searchText = (event as React.BaseSyntheticEvent)?.target?.value || '';
|
||||||
const filteredDashboards = filterDashboard(
|
const filteredDashboards = filterDashboard(
|
||||||
searchText,
|
searchText,
|
||||||
dashboardListResponse || [],
|
dashboardListResponse?.data || [],
|
||||||
);
|
);
|
||||||
setDashboards(filteredDashboards);
|
setDashboards(filteredDashboards);
|
||||||
setIsFilteringDashboards(false);
|
setIsFilteringDashboards(false);
|
||||||
@ -677,7 +673,7 @@ function DashboardsList(): JSX.Element {
|
|||||||
!isUndefined(dashboardListResponse)
|
!isUndefined(dashboardListResponse)
|
||||||
) {
|
) {
|
||||||
logEvent('Dashboard List: Page visited', {
|
logEvent('Dashboard List: Page visited', {
|
||||||
number: dashboardListResponse?.length,
|
number: dashboardListResponse?.data?.length,
|
||||||
});
|
});
|
||||||
logEventCalledRef.current = true;
|
logEventCalledRef.current = true;
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,21 @@ import {
|
|||||||
UploadProps,
|
UploadProps,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
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 ROUTES from 'constants/routes';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
||||||
import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
|
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/
|
// #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
|
// See more: https://github.com/lucide-icons/lucide/issues/94
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
import { DashboardData } from 'types/api/dashboard/getAll';
|
import { DashboardData } from 'types/api/dashboard/getAll';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
function ImportJSON({
|
function ImportJSON({
|
||||||
isImportJSONModalVisible,
|
isImportJSONModalVisible,
|
||||||
@ -74,6 +76,8 @@ function ImportJSON({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { showErrorModal } = useErrorModal();
|
||||||
|
|
||||||
const onClickLoadJsonHandler = async (): Promise<void> => {
|
const onClickLoadJsonHandler = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setDashboardCreating(true);
|
setDashboardCreating(true);
|
||||||
@ -81,11 +85,6 @@ function ImportJSON({
|
|||||||
|
|
||||||
const dashboardData = JSON.parse(editorValue) as DashboardData;
|
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) {
|
if (dashboardData?.layout) {
|
||||||
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
||||||
} else {
|
} else {
|
||||||
@ -97,28 +96,19 @@ function ImportJSON({
|
|||||||
uploadedGrafana,
|
uploadedGrafana,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
safeNavigate(
|
||||||
safeNavigate(
|
generatePath(ROUTES.DASHBOARD, {
|
||||||
generatePath(ROUTES.DASHBOARD, {
|
dashboardId: response.data.id,
|
||||||
dashboardId: response.payload.uuid,
|
}),
|
||||||
}),
|
);
|
||||||
);
|
logEvent('Dashboard List: New dashboard imported successfully', {
|
||||||
logEvent('Dashboard List: New dashboard imported successfully', {
|
dashboardId: response.data?.id,
|
||||||
dashboardId: response.payload?.uuid,
|
dashboardName: response.data?.data?.title,
|
||||||
dashboardName: response.payload?.data?.title,
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setIsCreateDashboardError(true);
|
|
||||||
notifications.error({
|
|
||||||
message:
|
|
||||||
response.error ||
|
|
||||||
t('something_went_wrong', {
|
|
||||||
ns: 'common',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setDashboardCreating(false);
|
setDashboardCreating(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
showErrorModal(error as APIError);
|
||||||
setDashboardCreating(false);
|
setDashboardCreating(false);
|
||||||
setIsCreateDashboardError(true);
|
setIsCreateDashboardError(true);
|
||||||
notifications.error({
|
notifications.error({
|
||||||
|
@ -6,8 +6,7 @@ import { executeSearchQueries } from '../utils';
|
|||||||
|
|
||||||
describe('executeSearchQueries', () => {
|
describe('executeSearchQueries', () => {
|
||||||
const firstDashboard: Dashboard = {
|
const firstDashboard: Dashboard = {
|
||||||
id: 11111,
|
id: uuid(),
|
||||||
uuid: uuid(),
|
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
createdBy: '',
|
createdBy: '',
|
||||||
@ -18,8 +17,7 @@ describe('executeSearchQueries', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
const secondDashboard: Dashboard = {
|
const secondDashboard: Dashboard = {
|
||||||
id: 22222,
|
id: uuid(),
|
||||||
uuid: uuid(),
|
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
createdBy: '',
|
createdBy: '',
|
||||||
@ -30,8 +28,7 @@ describe('executeSearchQueries', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
const thirdDashboard: Dashboard = {
|
const thirdDashboard: Dashboard = {
|
||||||
id: 333333,
|
id: uuid(),
|
||||||
uuid: uuid(),
|
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
createdBy: '',
|
createdBy: '',
|
||||||
|
@ -59,7 +59,7 @@ export function DeleteButton({
|
|||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
deleteDashboardMutation.mutateAsync(undefined, {
|
deleteDashboardMutation.mutate(undefined, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: t('dashboard:delete_dashboard_success', {
|
message: t('dashboard:delete_dashboard_success', {
|
||||||
|
@ -14,7 +14,7 @@ export const generateSearchData = (
|
|||||||
|
|
||||||
dashboards.forEach((dashboard) => {
|
dashboards.forEach((dashboard) => {
|
||||||
dashboardSearchData.push({
|
dashboardSearchData.push({
|
||||||
id: dashboard.uuid,
|
id: dashboard.id,
|
||||||
title: dashboard.data.title,
|
title: dashboard.data.title,
|
||||||
description: dashboard.data.description,
|
description: dashboard.data.description,
|
||||||
tags: dashboard.data.tags || [],
|
tags: dashboard.data.tags || [],
|
||||||
|
@ -21,8 +21,10 @@ import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
|||||||
|
|
||||||
import { DataType } from '../TableView';
|
import { DataType } from '../TableView';
|
||||||
import {
|
import {
|
||||||
|
escapeHtml,
|
||||||
filterKeyForField,
|
filterKeyForField,
|
||||||
jsonToDataNodes,
|
jsonToDataNodes,
|
||||||
|
parseFieldValue,
|
||||||
recursiveParseJSON,
|
recursiveParseJSON,
|
||||||
removeEscapeCharacters,
|
removeEscapeCharacters,
|
||||||
unescapeString,
|
unescapeString,
|
||||||
@ -85,7 +87,7 @@ export function TableViewActions(
|
|||||||
record.field === 'body'
|
record.field === 'body'
|
||||||
? {
|
? {
|
||||||
__html: convert.toHtml(
|
__html: convert.toHtml(
|
||||||
dompurify.sanitize(unescapeString(record.value), {
|
dompurify.sanitize(unescapeString(escapeHtml(record.value)), {
|
||||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -155,7 +157,11 @@ export function TableViewActions(
|
|||||||
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onClick={onClickHandler(OPERATORS['='], fieldFilterKey, fieldData.value)}
|
onClick={onClickHandler(
|
||||||
|
OPERATORS['='],
|
||||||
|
fieldFilterKey,
|
||||||
|
parseFieldValue(fieldData.value),
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Filter out value">
|
<Tooltip title="Filter out value">
|
||||||
@ -171,7 +177,7 @@ export function TableViewActions(
|
|||||||
onClick={onClickHandler(
|
onClick={onClickHandler(
|
||||||
OPERATORS['!='],
|
OPERATORS['!='],
|
||||||
fieldFilterKey,
|
fieldFilterKey,
|
||||||
fieldData.value,
|
parseFieldValue(fieldData.value),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -259,6 +259,24 @@ export const getDataTypes = (value: unknown): DataTypes => {
|
|||||||
return determineType(value);
|
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
|
// 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
|
// the log line readable
|
||||||
export const removeEscapeCharacters = (str: string): string =>
|
export const removeEscapeCharacters = (str: string): string =>
|
||||||
|
@ -28,16 +28,12 @@ import LogsExplorerTable from 'container/LogsExplorerTable';
|
|||||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
|
||||||
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
|
||||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import useAxiosError from 'hooks/useAxiosError';
|
|
||||||
import useClickOutside from 'hooks/useClickOutside';
|
import useClickOutside from 'hooks/useClickOutside';
|
||||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
@ -98,7 +94,6 @@ function LogsExplorerViews({
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
chartQueryKeyRef: MutableRefObject<any>;
|
chartQueryKeyRef: MutableRefObject<any>;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
|
||||||
const { safeNavigate } = useSafeNavigate();
|
const { safeNavigate } = useSafeNavigate();
|
||||||
|
|
||||||
// this is to respect the panel type present in the URL rather than defaulting it to list always.
|
// 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 [queryId, setQueryId] = useState<string>(v4());
|
||||||
const [queryStats, setQueryStats] = useState<WsDataEvent>();
|
const [queryStats, setQueryStats] = useState<WsDataEvent>();
|
||||||
|
|
||||||
const handleAxisError = useAxiosError();
|
|
||||||
|
|
||||||
const listQuery = useMemo(() => {
|
const listQuery = useMemo(() => {
|
||||||
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
|
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
|
||||||
|
|
||||||
@ -396,11 +389,6 @@ function LogsExplorerViews({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [data?.payload]);
|
}, [data?.payload]);
|
||||||
|
|
||||||
const {
|
|
||||||
mutate: updateDashboard,
|
|
||||||
isLoading: isUpdateDashboardLoading,
|
|
||||||
} = useUpdateDashboard();
|
|
||||||
|
|
||||||
const getUpdatedQueryForExport = useCallback((): Query => {
|
const getUpdatedQueryForExport = useCallback((): Query => {
|
||||||
const updatedQuery = cloneDeep(currentQuery);
|
const updatedQuery = cloneDeep(currentQuery);
|
||||||
|
|
||||||
@ -424,68 +412,22 @@ function LogsExplorerViews({
|
|||||||
? getUpdatedQueryForExport()
|
? getUpdatedQueryForExport()
|
||||||
: exportDefaultQuery;
|
: exportDefaultQuery;
|
||||||
|
|
||||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
|
||||||
dashboard,
|
|
||||||
query,
|
|
||||||
widgetId,
|
|
||||||
panelTypeParam,
|
|
||||||
options.selectColumns,
|
|
||||||
);
|
|
||||||
|
|
||||||
logEvent('Logs Explorer: Add to dashboard successful', {
|
logEvent('Logs Explorer: Add to dashboard successful', {
|
||||||
panelType,
|
panelType,
|
||||||
isNewDashboard,
|
isNewDashboard,
|
||||||
dashboardName: dashboard?.data?.title,
|
dashboardName: dashboard?.data?.title,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateDashboard(updatedDashboard, {
|
const dashboardEditView = generateExportToDashboardLink({
|
||||||
onSuccess: (data) => {
|
query,
|
||||||
if (data.error) {
|
panelType: panelTypeParam,
|
||||||
const message =
|
dashboardId: dashboard.id,
|
||||||
data.error === 'feature usage exceeded' ? (
|
widgetId,
|
||||||
<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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
safeNavigate(dashboardEditView);
|
||||||
},
|
},
|
||||||
[
|
[getUpdatedQueryForExport, exportDefaultQuery, safeNavigate, panelType],
|
||||||
getUpdatedQueryForExport,
|
|
||||||
exportDefaultQuery,
|
|
||||||
options.selectColumns,
|
|
||||||
safeNavigate,
|
|
||||||
notifications,
|
|
||||||
panelType,
|
|
||||||
updateDashboard,
|
|
||||||
handleAxisError,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -811,7 +753,6 @@ function LogsExplorerViews({
|
|||||||
<ExplorerOptionWrapper
|
<ExplorerOptionWrapper
|
||||||
disabled={!stagedQuery}
|
disabled={!stagedQuery}
|
||||||
query={exportDefaultQuery}
|
query={exportDefaultQuery}
|
||||||
isLoading={isUpdateDashboardLoading}
|
|
||||||
onExport={handleExport}
|
onExport={handleExport}
|
||||||
sourcepage={DataSource.LOGS}
|
sourcepage={DataSource.LOGS}
|
||||||
/>
|
/>
|
||||||
|
@ -2,18 +2,12 @@ import './Explorer.styles.scss';
|
|||||||
|
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Switch } from 'antd';
|
import { Switch } from 'antd';
|
||||||
import axios from 'axios';
|
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
|
||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
|
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
|
||||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
|
||||||
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
|
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
|
||||||
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
|
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 { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
@ -39,13 +33,6 @@ function Explorer(): JSX.Element {
|
|||||||
currentQuery,
|
currentQuery,
|
||||||
} = useQueryBuilder();
|
} = useQueryBuilder();
|
||||||
const { safeNavigate } = useSafeNavigate();
|
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 [searchParams, setSearchParams] = useSearchParams();
|
||||||
const isOneChartPerQueryEnabled =
|
const isOneChartPerQueryEnabled =
|
||||||
@ -86,59 +73,16 @@ function Explorer(): JSX.Element {
|
|||||||
|
|
||||||
const widgetId = uuid();
|
const widgetId = uuid();
|
||||||
|
|
||||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
const dashboardEditView = generateExportToDashboardLink({
|
||||||
dashboard,
|
query: queryToExport || exportDefaultQuery,
|
||||||
queryToExport || exportDefaultQuery,
|
panelType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
dashboardId: dashboard.id,
|
||||||
widgetId,
|
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, safeNavigate],
|
||||||
[exportDefaultQuery, notifications, updateDashboard],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const splitedQueries = useMemo(
|
const splitedQueries = useMemo(
|
||||||
@ -201,7 +145,6 @@ function Explorer(): JSX.Element {
|
|||||||
<ExplorerOptionWrapper
|
<ExplorerOptionWrapper
|
||||||
disabled={!stagedQuery}
|
disabled={!stagedQuery}
|
||||||
query={exportDefaultQuery}
|
query={exportDefaultQuery}
|
||||||
isLoading={isLoading}
|
|
||||||
sourcepage={DataSource.METRICS}
|
sourcepage={DataSource.METRICS}
|
||||||
onExport={handleExport}
|
onExport={handleExport}
|
||||||
isOneChartPerQuery={showOneChartPerQuery}
|
isOneChartPerQuery={showOneChartPerQuery}
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
@ -44,11 +43,8 @@ import { FullScreenHandle } from 'react-full-screen';
|
|||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
import {
|
import { DashboardData, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
Dashboard,
|
import { Props } from 'types/api/dashboard/update';
|
||||||
DashboardData,
|
|
||||||
IDashboardVariable,
|
|
||||||
} from 'types/api/dashboard/getAll';
|
|
||||||
import { ROLES, USER_ROLES } from 'types/roles';
|
import { ROLES, USER_ROLES } from 'types/roles';
|
||||||
import { ComponentTypes } from 'utils/permission';
|
import { ComponentTypes } from 'utils/permission';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@ -65,10 +61,9 @@ interface DashboardDescriptionProps {
|
|||||||
|
|
||||||
export function sanitizeDashboardData(
|
export function sanitizeDashboardData(
|
||||||
selectedData: DashboardData,
|
selectedData: DashboardData,
|
||||||
): Omit<DashboardData, 'uuid'> {
|
): DashboardData {
|
||||||
if (!selectedData?.variables) {
|
if (!selectedData?.variables) {
|
||||||
const { uuid, ...rest } = selectedData;
|
return selectedData;
|
||||||
return rest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedVariables = Object.entries(selectedData.variables).reduce(
|
const updatedVariables = Object.entries(selectedData.variables).reduce(
|
||||||
@ -80,9 +75,8 @@ export function sanitizeDashboardData(
|
|||||||
{} as Record<string, IDashboardVariable>,
|
{} as Record<string, IDashboardVariable>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { uuid, ...restData } = selectedData;
|
|
||||||
return {
|
return {
|
||||||
...restData,
|
...selectedData,
|
||||||
variables: updatedVariables,
|
variables: updatedVariables,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -108,7 +102,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
const selectedData = selectedDashboard
|
const selectedData = selectedDashboard
|
||||||
? {
|
? {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
uuid: selectedDashboard.uuid,
|
uuid: selectedDashboard.id,
|
||||||
}
|
}
|
||||||
: ({} as DashboardData);
|
: ({} as DashboardData);
|
||||||
|
|
||||||
@ -162,7 +156,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
setSelectedRowWidgetId(null);
|
setSelectedRowWidgetId(null);
|
||||||
handleToggleDashboardSlider(true);
|
handleToggleDashboardSlider(true);
|
||||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||||
dashboardId: selectedDashboard?.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
dashboardName: selectedDashboard?.data.title,
|
dashboardName: selectedDashboard?.data.title,
|
||||||
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
||||||
});
|
});
|
||||||
@ -178,8 +172,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
if (!selectedDashboard) {
|
if (!selectedDashboard) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const updatedDashboard = {
|
const updatedDashboard: Props = {
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
title: updatedTitle,
|
title: updatedTitle,
|
||||||
@ -191,13 +186,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
message: 'Dashboard renamed successfully',
|
message: 'Dashboard renamed successfully',
|
||||||
});
|
});
|
||||||
setIsRenameDashboardOpen(false);
|
setIsRenameDashboardOpen(false);
|
||||||
if (updatedDashboard.payload)
|
if (updatedDashboard.data) setSelectedDashboard(updatedDashboard.data);
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
notifications.error({
|
|
||||||
message: SOMETHING_WENT_WRONG,
|
|
||||||
});
|
|
||||||
setIsRenameDashboardOpen(true);
|
setIsRenameDashboardOpen(true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -251,8 +242,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedDashboard: Dashboard = {
|
const updatedDashboard: Props = {
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
layout: [
|
layout: [
|
||||||
@ -279,28 +271,21 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateDashboardMutation.mutate(updatedDashboard, {
|
updateDashboardMutation.mutate(updatedDashboard, {
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (updatedDashboard.payload) {
|
if (updatedDashboard.data) {
|
||||||
if (updatedDashboard.payload.data.layout)
|
if (updatedDashboard.data.data.layout)
|
||||||
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsPanelNameModalOpen(false);
|
setIsPanelNameModalOpen(false);
|
||||||
setSectionName(DEFAULT_ROW_NAME);
|
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
|
<DeleteButton
|
||||||
createdBy={selectedDashboard?.createdBy || ''}
|
createdBy={selectedDashboard?.createdBy || ''}
|
||||||
name={selectedDashboard?.data.title || ''}
|
name={selectedDashboard?.data.title || ''}
|
||||||
id={String(selectedDashboard?.uuid) || ''}
|
id={String(selectedDashboard?.id) || ''}
|
||||||
isLocked={isDashboardLocked}
|
isLocked={isDashboardLocked}
|
||||||
routeToListPage
|
routeToListPage
|
||||||
/>
|
/>
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import './GeneralSettings.styles.scss';
|
import './GeneralSettings.styles.scss';
|
||||||
|
|
||||||
import { Col, Input, Select, Space, Typography } from 'antd';
|
import { Col, Input, Select, Space, Typography } from 'antd';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
|
||||||
import AddTags from 'container/NewDashboard/DashboardSettings/General/AddTags';
|
import AddTags from 'container/NewDashboard/DashboardSettings/General/AddTags';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { Check, X } from 'lucide-react';
|
import { Check, X } from 'lucide-react';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
@ -38,14 +36,12 @@ function GeneralDashboardSettings(): JSX.Element {
|
|||||||
|
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
const onSaveHandler = (): void => {
|
const onSaveHandler = (): void => {
|
||||||
if (!selectedDashboard) return;
|
if (!selectedDashboard) return;
|
||||||
|
|
||||||
updateDashboardMutation.mutateAsync(
|
updateDashboardMutation.mutate(
|
||||||
{
|
{
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
description: updatedDescription,
|
description: updatedDescription,
|
||||||
@ -56,15 +52,11 @@ function GeneralDashboardSettings(): JSX.Element {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (updatedDashboard.payload) {
|
if (updatedDashboard.data) {
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {},
|
||||||
notifications.error({
|
|
||||||
message: SOMETHING_WENT_WRONG,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -171,7 +171,8 @@ function VariablesSetting({
|
|||||||
|
|
||||||
updateMutation.mutateAsync(
|
updateMutation.mutateAsync(
|
||||||
{
|
{
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
variables: updatedVariablesData,
|
variables: updatedVariablesData,
|
||||||
@ -179,18 +180,13 @@ function VariablesSetting({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (updatedDashboard.payload) {
|
if (updatedDashboard.data) {
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: t('variable_updated_successfully'),
|
message: t('variable_updated_successfully'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
|
||||||
notifications.error({
|
|
||||||
message: t('error_while_updating_variable'),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -127,7 +127,7 @@ function QuerySection({
|
|||||||
panelType: selectedWidget.panelTypes,
|
panelType: selectedWidget.panelTypes,
|
||||||
queryType: currentQuery.queryType,
|
queryType: currentQuery.queryType,
|
||||||
widgetId: selectedWidget.id,
|
widgetId: selectedWidget.id,
|
||||||
dashboardId: selectedDashboard?.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
dashboardName: selectedDashboard?.data.title,
|
dashboardName: selectedDashboard?.data.title,
|
||||||
isNewPanel,
|
isNewPanel,
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,6 @@ import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants';
|
|||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import useAxiosError from 'hooks/useAxiosError';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
@ -41,10 +40,10 @@ import { AppState } from 'store/reducers';
|
|||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import {
|
import {
|
||||||
ColumnUnit,
|
ColumnUnit,
|
||||||
Dashboard,
|
|
||||||
LegendPosition,
|
LegendPosition,
|
||||||
Widgets,
|
Widgets,
|
||||||
} from 'types/api/dashboard/getAll';
|
} from 'types/api/dashboard/getAll';
|
||||||
|
import { Props } from 'types/api/dashboard/update';
|
||||||
import { IField } from 'types/api/logs/fields';
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
@ -141,7 +140,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
if (!logEventCalledRef.current) {
|
if (!logEventCalledRef.current) {
|
||||||
logEvent('Panel Edit: Page visited', {
|
logEvent('Panel Edit: Page visited', {
|
||||||
panelType: selectedWidget?.panelTypes,
|
panelType: selectedWidget?.panelTypes,
|
||||||
dashboardId: selectedDashboard?.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
widgetId: selectedWidget?.id,
|
widgetId: selectedWidget?.id,
|
||||||
dashboardName: selectedDashboard?.data.title,
|
dashboardName: selectedDashboard?.data.title,
|
||||||
isNewPanel: !!isWidgetNotPresent,
|
isNewPanel: !!isWidgetNotPresent,
|
||||||
@ -345,8 +344,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
return { selectedWidget, preWidgets, afterWidgets };
|
return { selectedWidget, preWidgets, afterWidgets };
|
||||||
}, [selectedDashboard, query]);
|
}, [selectedDashboard, query]);
|
||||||
|
|
||||||
const handleError = useAxiosError();
|
|
||||||
|
|
||||||
// this loading state is to take care of mismatch in the responses for table and other panels
|
// 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
|
// hence while changing the query contains the older value and the processing logic fails
|
||||||
const [isLoadingPanelData, setIsLoadingPanelData] = useState<boolean>(false);
|
const [isLoadingPanelData, setIsLoadingPanelData] = useState<boolean>(false);
|
||||||
@ -470,9 +467,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
updatedLayout = newLayoutItem;
|
updatedLayout = newLayoutItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dashboard: Dashboard = {
|
const dashboard: Props = {
|
||||||
...selectedDashboard,
|
id: selectedDashboard.id,
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
widgets: isNewDashboard
|
widgets: isNewDashboard
|
||||||
@ -540,15 +537,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateDashboardMutation.mutateAsync(dashboard, {
|
updateDashboardMutation.mutateAsync(dashboard, {
|
||||||
onSuccess: () => {
|
onSuccess: (updatedDashboard) => {
|
||||||
setSelectedRowWidgetId(null);
|
setSelectedRowWidgetId(null);
|
||||||
setSelectedDashboard(dashboard);
|
setSelectedDashboard(updatedDashboard.data);
|
||||||
setToScrollWidgetId(selectedWidget?.id || '');
|
setToScrollWidgetId(selectedWidget?.id || '');
|
||||||
safeNavigate({
|
safeNavigate({
|
||||||
pathname: generatePath(ROUTES.DASHBOARD, { dashboardId }),
|
pathname: generatePath(ROUTES.DASHBOARD, { dashboardId }),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: handleError,
|
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
@ -562,7 +558,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
currentQuery,
|
currentQuery,
|
||||||
preWidgets,
|
preWidgets,
|
||||||
updateDashboardMutation,
|
updateDashboardMutation,
|
||||||
handleError,
|
|
||||||
widgets,
|
widgets,
|
||||||
setSelectedDashboard,
|
setSelectedDashboard,
|
||||||
setToScrollWidgetId,
|
setToScrollWidgetId,
|
||||||
@ -601,7 +596,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
|
|
||||||
logEvent('Panel Edit: Save changes', {
|
logEvent('Panel Edit: Save changes', {
|
||||||
panelType: selectedWidget.panelTypes,
|
panelType: selectedWidget.panelTypes,
|
||||||
dashboardId: selectedDashboard?.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
widgetId: selectedWidget.id,
|
widgetId: selectedWidget.id,
|
||||||
dashboardName: selectedDashboard?.data.title,
|
dashboardName: selectedDashboard?.data.title,
|
||||||
queryType: currentQuery.queryType,
|
queryType: currentQuery.queryType,
|
||||||
|
@ -26,6 +26,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { LicenseStatus } from 'types/api/licensesV3/getActive';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { USER_ROLES } from 'types/roles';
|
import { USER_ROLES } from 'types/roles';
|
||||||
import { checkVersionState } from 'utils/app';
|
import { checkVersionState } from 'utils/app';
|
||||||
@ -301,10 +302,11 @@ function SideNav(): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isOnBasicPlan =
|
const isOnBasicPlan =
|
||||||
activeLicenseFetchError &&
|
(activeLicenseFetchError &&
|
||||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||||
activeLicenseFetchError?.getHttpStatusCode(),
|
activeLicenseFetchError?.getHttpStatusCode(),
|
||||||
);
|
)) ||
|
||||||
|
(activeLicense?.status && activeLicense.status === LicenseStatus.INVALID);
|
||||||
|
|
||||||
if (user.role !== USER_ROLES.ADMIN || isOnBasicPlan) {
|
if (user.role !== USER_ROLES.ADMIN || isOnBasicPlan) {
|
||||||
updatedMenuItems = updatedMenuItems.filter(
|
updatedMenuItems = updatedMenuItems.filter(
|
||||||
@ -353,6 +355,7 @@ function SideNav(): JSX.Element {
|
|||||||
t,
|
t,
|
||||||
user.role,
|
user.role,
|
||||||
activeLicenseFetchError,
|
activeLicenseFetchError,
|
||||||
|
activeLicense?.status,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
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 { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||||
import { useMutation, UseMutationResult } from 'react-query';
|
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 = (
|
export const useDeleteDashboard = (
|
||||||
id: string,
|
id: string,
|
||||||
): UseMutationResult<PayloadProps, unknown, void, unknown> =>
|
): UseMutationResult<SuccessResponseV2<null>, APIError, void, unknown> => {
|
||||||
useMutation({
|
const { showErrorModal } = useErrorModal();
|
||||||
|
|
||||||
|
return useMutation<SuccessResponseV2<null>, APIError>({
|
||||||
mutationKey: REACT_QUERY_KEY.DELETE_DASHBOARD,
|
mutationKey: REACT_QUERY_KEY.DELETE_DASHBOARD,
|
||||||
mutationFn: () =>
|
mutationFn: () =>
|
||||||
deleteDashboard({
|
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 { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||||
import { useQuery, UseQueryResult } from 'react-query';
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
export const useGetAllDashboard = (): UseQueryResult<Dashboard[], unknown> =>
|
export const useGetAllDashboard = (): UseQueryResult<
|
||||||
useQuery<Dashboard[]>({
|
SuccessResponseV2<Dashboard[]>,
|
||||||
queryFn: getAllDashboardList,
|
APIError
|
||||||
|
> => {
|
||||||
|
const { showErrorModal } = useErrorModal();
|
||||||
|
|
||||||
|
return useQuery<SuccessResponseV2<Dashboard[]>, APIError>({
|
||||||
|
queryFn: getAll,
|
||||||
|
onError: (error) => {
|
||||||
|
showErrorModal(error);
|
||||||
|
},
|
||||||
queryKey: REACT_QUERY_KEY.GET_ALL_DASHBOARDS,
|
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 dayjs from 'dayjs';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||||
import { useMutation, UseMutationResult } from 'react-query';
|
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 { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
import { Props } from 'types/api/dashboard/update';
|
import { Props } from 'types/api/dashboard/update';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
export const useUpdateDashboard = (): UseUpdateDashboard => {
|
export const useUpdateDashboard = (): UseUpdateDashboard => {
|
||||||
const { updatedTimeRef } = useDashboard();
|
const { updatedTimeRef } = useDashboard();
|
||||||
|
const { showErrorModal } = useErrorModal();
|
||||||
return useMutation(update, {
|
return useMutation(update, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.payload) {
|
if (data.data) {
|
||||||
updatedTimeRef.current = dayjs(data.payload.updatedAt);
|
updatedTimeRef.current = dayjs(data.data.updatedAt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showErrorModal(error);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
type UseUpdateDashboard = UseMutationResult<
|
type UseUpdateDashboard = UseMutationResult<
|
||||||
SuccessResponse<Dashboard> | ErrorResponse,
|
SuccessResponseV2<Dashboard>,
|
||||||
unknown,
|
APIError,
|
||||||
Props,
|
Props,
|
||||||
unknown
|
unknown
|
||||||
>;
|
>;
|
||||||
|
@ -38,7 +38,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
|||||||
logEvent('Panel Edit: Create alert', {
|
logEvent('Panel Edit: Create alert', {
|
||||||
panelType: widget.panelTypes,
|
panelType: widget.panelTypes,
|
||||||
dashboardName: selectedDashboard?.data?.title,
|
dashboardName: selectedDashboard?.data?.title,
|
||||||
dashboardId: selectedDashboard?.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
widgetId: widget.id,
|
widgetId: widget.id,
|
||||||
queryType: widget.query.queryType,
|
queryType: widget.query.queryType,
|
||||||
});
|
});
|
||||||
@ -47,7 +47,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
|||||||
action: MenuItemKeys.CreateAlerts,
|
action: MenuItemKeys.CreateAlerts,
|
||||||
panelType: widget.panelTypes,
|
panelType: widget.panelTypes,
|
||||||
dashboardName: selectedDashboard?.data?.title,
|
dashboardName: selectedDashboard?.data?.title,
|
||||||
dashboardId: selectedDashboard?.uuid,
|
dashboardId: selectedDashboard?.id,
|
||||||
widgetId: widget.id,
|
widgetId: widget.id,
|
||||||
queryType: widget.query.queryType,
|
queryType: widget.query.queryType,
|
||||||
});
|
});
|
||||||
|
@ -3,8 +3,7 @@ export const dashboardSuccessResponse = {
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: '1',
|
||||||
uuid: '1',
|
|
||||||
createdAt: '2022-11-16T13:29:47.064874419Z',
|
createdAt: '2022-11-16T13:29:47.064874419Z',
|
||||||
createdBy: null,
|
createdBy: null,
|
||||||
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
||||||
@ -23,8 +22,7 @@ export const dashboardSuccessResponse = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: '2',
|
||||||
uuid: '2',
|
|
||||||
createdAt: '2022-11-16T13:20:47.064874419Z',
|
createdAt: '2022-11-16T13:20:47.064874419Z',
|
||||||
createdBy: null,
|
createdBy: null,
|
||||||
updatedAt: '2024-05-21T06:42:30.546630961Z',
|
updatedAt: '2024-05-21T06:42:30.546630961Z',
|
||||||
@ -53,8 +51,7 @@ export const dashboardEmptyState = {
|
|||||||
export const getDashboardById = {
|
export const getDashboardById = {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: {
|
data: {
|
||||||
id: 1,
|
id: '1',
|
||||||
uuid: '1',
|
|
||||||
createdAt: '2022-11-16T13:29:47.064874419Z',
|
createdAt: '2022-11-16T13:29:47.064874419Z',
|
||||||
createdBy: 'integration',
|
createdBy: 'integration',
|
||||||
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
||||||
@ -78,8 +75,7 @@ export const getDashboardById = {
|
|||||||
export const getNonIntegrationDashboardById = {
|
export const getNonIntegrationDashboardById = {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: {
|
data: {
|
||||||
id: 1,
|
id: '1',
|
||||||
uuid: '1',
|
|
||||||
createdAt: '2022-11-16T13:29:47.064874419Z',
|
createdAt: '2022-11-16T13:29:47.064874419Z',
|
||||||
createdBy: 'thor',
|
createdBy: 'thor',
|
||||||
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
updatedAt: '2024-05-21T06:41:30.546630961Z',
|
||||||
|
@ -234,7 +234,6 @@ describe('dashboard list page', () => {
|
|||||||
const firstDashboardData = dashboardSuccessResponse.data[0];
|
const firstDashboardData = dashboardSuccessResponse.data[0];
|
||||||
expect(dashboardUtils.sanitizeDashboardData).toHaveBeenCalledWith(
|
expect(dashboardUtils.sanitizeDashboardData).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: firstDashboardData.uuid,
|
|
||||||
title: firstDashboardData.data.title,
|
title: firstDashboardData.data.title,
|
||||||
createdAt: firstDashboardData.createdAt,
|
createdAt: firstDashboardData.createdAt,
|
||||||
}),
|
}),
|
||||||
|
@ -19,9 +19,9 @@ function DashboardPage(): JSX.Element {
|
|||||||
: 'Something went wrong';
|
: 'Something went wrong';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dashboardTitle = dashboardResponse.data?.data.title;
|
const dashboardTitle = dashboardResponse.data?.data.data.title;
|
||||||
document.title = dashboardTitle || document.title;
|
document.title = dashboardTitle || document.title;
|
||||||
}, [dashboardResponse.data?.data.title, isFetching]);
|
}, [dashboardResponse.data?.data.data.title, isFetching]);
|
||||||
|
|
||||||
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {
|
if (isError && !isFetching && errorMessage === ErrorType.NotFound) {
|
||||||
return <NotFound />;
|
return <NotFound />;
|
||||||
|
@ -4,7 +4,6 @@ import { FilterOutlined } from '@ant-design/icons';
|
|||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Button, Card, Tabs, Tooltip } from 'antd';
|
import { Button, Card, Tabs, Tooltip } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import axios from 'axios';
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
|
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
|
||||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||||
@ -19,13 +18,10 @@ import RightToolbarActions from 'container/QueryBuilder/components/ToolbarAction
|
|||||||
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
|
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
|
||||||
import { defaultSelectedColumns } from 'container/TracesExplorer/ListView/configs';
|
import { defaultSelectedColumns } from 'container/TracesExplorer/ListView/configs';
|
||||||
import QuerySection from 'container/TracesExplorer/QuerySection';
|
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 { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import { cloneDeep, isEmpty, set } from 'lodash-es';
|
import { cloneDeep, isEmpty, set } from 'lodash-es';
|
||||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
@ -40,8 +36,6 @@ import { ActionsWrapper, Container } from './styles';
|
|||||||
import { getTabsItems } from './utils';
|
import { getTabsItems } from './utils';
|
||||||
|
|
||||||
function TracesExplorer(): JSX.Element {
|
function TracesExplorer(): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentQuery,
|
currentQuery,
|
||||||
panelType,
|
panelType,
|
||||||
@ -124,9 +118,7 @@ function TracesExplorer(): JSX.Element {
|
|||||||
[currentQuery, updateAllQueriesOperators],
|
[currentQuery, updateAllQueriesOperators],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutate: updateDashboard, isLoading } = useUpdateDashboard();
|
const getUpdatedQueryForExport = useCallback((): Query => {
|
||||||
|
|
||||||
const getUpdatedQueryForExport = (): Query => {
|
|
||||||
const updatedQuery = cloneDeep(currentQuery);
|
const updatedQuery = cloneDeep(currentQuery);
|
||||||
|
|
||||||
set(
|
set(
|
||||||
@ -136,7 +128,7 @@ function TracesExplorer(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return updatedQuery;
|
return updatedQuery;
|
||||||
};
|
}, [currentQuery, options.selectColumns]);
|
||||||
|
|
||||||
const handleExport = useCallback(
|
const handleExport = useCallback(
|
||||||
(dashboard: Dashboard | null, isNewDashboard?: boolean): void => {
|
(dashboard: Dashboard | null, isNewDashboard?: boolean): void => {
|
||||||
@ -153,65 +145,22 @@ function TracesExplorer(): JSX.Element {
|
|||||||
? getUpdatedQueryForExport()
|
? getUpdatedQueryForExport()
|
||||||
: exportDefaultQuery;
|
: exportDefaultQuery;
|
||||||
|
|
||||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
|
||||||
dashboard,
|
|
||||||
query,
|
|
||||||
widgetId,
|
|
||||||
panelTypeParam,
|
|
||||||
options.selectColumns,
|
|
||||||
);
|
|
||||||
|
|
||||||
logEvent('Traces Explorer: Add to dashboard successful', {
|
logEvent('Traces Explorer: Add to dashboard successful', {
|
||||||
panelType,
|
panelType,
|
||||||
isNewDashboard,
|
isNewDashboard,
|
||||||
dashboardName: dashboard?.data?.title,
|
dashboardName: dashboard?.data?.title,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateDashboard(updatedDashboard, {
|
const dashboardEditView = generateExportToDashboardLink({
|
||||||
onSuccess: (data) => {
|
query,
|
||||||
if (data.error) {
|
panelType: panelTypeParam,
|
||||||
const message =
|
dashboardId: dashboard.id,
|
||||||
data.error === 'feature usage exceeded' ? (
|
widgetId,
|
||||||
<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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
safeNavigate(dashboardEditView);
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
[exportDefaultQuery, panelType, safeNavigate, getUpdatedQueryForExport],
|
||||||
[exportDefaultQuery, notifications, panelType, updateDashboard],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useShareBuilderUrl(defaultQuery);
|
useShareBuilderUrl(defaultQuery);
|
||||||
@ -282,11 +231,7 @@ function TracesExplorer(): JSX.Element {
|
|||||||
|
|
||||||
<Container className="traces-explorer-views">
|
<Container className="traces-explorer-views">
|
||||||
<ActionsWrapper>
|
<ActionsWrapper>
|
||||||
<ExportPanel
|
<ExportPanel query={exportDefaultQuery} onExport={handleExport} />
|
||||||
query={exportDefaultQuery}
|
|
||||||
isLoading={isLoading}
|
|
||||||
onExport={handleExport}
|
|
||||||
/>
|
|
||||||
</ActionsWrapper>
|
</ActionsWrapper>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
@ -299,7 +244,6 @@ function TracesExplorer(): JSX.Element {
|
|||||||
<ExplorerOptionWrapper
|
<ExplorerOptionWrapper
|
||||||
disabled={!stagedQuery}
|
disabled={!stagedQuery}
|
||||||
query={exportDefaultQuery}
|
query={exportDefaultQuery}
|
||||||
isLoading={isLoading}
|
|
||||||
sourcepage={DataSource.TRACES}
|
sourcepage={DataSource.TRACES}
|
||||||
onExport={handleExport}
|
onExport={handleExport}
|
||||||
/>
|
/>
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import { Modal } from 'antd';
|
import { Modal } from 'antd';
|
||||||
import getDashboard from 'api/dashboard/get';
|
import getDashboard from 'api/v1/dashboards/id/get';
|
||||||
import lockDashboardApi from 'api/dashboard/lockDashboard';
|
import locked from 'api/v1/dashboards/id/lock';
|
||||||
import unlockDashboardApi from 'api/dashboard/unlockDashboard';
|
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { useDashboardVariablesFromLocalStorage } from 'hooks/dashboard/useDashboardFromLocalStorage';
|
import { useDashboardVariablesFromLocalStorage } from 'hooks/dashboard/useDashboardFromLocalStorage';
|
||||||
import useAxiosError from 'hooks/useAxiosError';
|
|
||||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||||
import useTabVisibility from 'hooks/useTabFocus';
|
import useTabVisibility from 'hooks/useTabFocus';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
@ -18,6 +16,7 @@ import isEqual from 'lodash-es/isEqual';
|
|||||||
import isUndefined from 'lodash-es/isUndefined';
|
import isUndefined from 'lodash-es/isUndefined';
|
||||||
import omitBy from 'lodash-es/omitBy';
|
import omitBy from 'lodash-es/omitBy';
|
||||||
import { useAppContext } from 'providers/App/App';
|
import { useAppContext } from 'providers/App/App';
|
||||||
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
@ -36,7 +35,9 @@ import { Dispatch } from 'redux';
|
|||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
|
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
|
||||||
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
@ -52,7 +53,10 @@ const DashboardContext = createContext<IDashboardContext>({
|
|||||||
isDashboardLocked: false,
|
isDashboardLocked: false,
|
||||||
handleToggleDashboardSlider: () => {},
|
handleToggleDashboardSlider: () => {},
|
||||||
handleDashboardLockToggle: () => {},
|
handleDashboardLockToggle: () => {},
|
||||||
dashboardResponse: {} as UseQueryResult<Dashboard, unknown>,
|
dashboardResponse: {} as UseQueryResult<
|
||||||
|
SuccessResponseV2<Dashboard>,
|
||||||
|
APIError
|
||||||
|
>,
|
||||||
selectedDashboard: {} as Dashboard,
|
selectedDashboard: {} as Dashboard,
|
||||||
dashboardId: '',
|
dashboardId: '',
|
||||||
layouts: [],
|
layouts: [],
|
||||||
@ -116,6 +120,8 @@ export function DashboardProvider({
|
|||||||
exact: true,
|
exact: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { showErrorModal } = useErrorModal();
|
||||||
|
|
||||||
// added extra checks here in case wrong values appear use the default values rather than empty dashboards
|
// added extra checks here in case wrong values appear use the default values rather than empty dashboards
|
||||||
const supportedOrderColumnKeys = ['createdAt', 'updatedAt'];
|
const supportedOrderColumnKeys = ['createdAt', 'updatedAt'];
|
||||||
|
|
||||||
@ -270,18 +276,24 @@ export function DashboardProvider({
|
|||||||
setIsDashboardFetching(true);
|
setIsDashboardFetching(true);
|
||||||
try {
|
try {
|
||||||
return await getDashboard({
|
return await getDashboard({
|
||||||
uuid: dashboardId,
|
id: dashboardId,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showErrorModal(error as APIError);
|
||||||
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
setIsDashboardFetching(false);
|
setIsDashboardFetching(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
onSuccess: (data) => {
|
onError: (error) => {
|
||||||
const updatedDashboardData = transformDashboardVariables(data);
|
showErrorModal(error as APIError);
|
||||||
|
},
|
||||||
|
onSuccess: (data: SuccessResponseV2<Dashboard>) => {
|
||||||
|
const updatedDashboardData = transformDashboardVariables(data?.data);
|
||||||
const updatedDate = dayjs(updatedDashboardData.updatedAt);
|
const updatedDate = dayjs(updatedDashboardData.updatedAt);
|
||||||
|
|
||||||
setIsDashboardLocked(updatedDashboardData?.isLocked || false);
|
setIsDashboardLocked(updatedDashboardData?.locked || false);
|
||||||
|
|
||||||
// on first render
|
// on first render
|
||||||
if (updatedTimeRef.current === null) {
|
if (updatedTimeRef.current === null) {
|
||||||
@ -387,29 +399,25 @@ export function DashboardProvider({
|
|||||||
setIsDashboardSlider(value);
|
setIsDashboardSlider(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleError = useAxiosError();
|
const { mutate: lockDashboard } = useMutation(locked, {
|
||||||
|
onSuccess: (_, props) => {
|
||||||
const { mutate: lockDashboard } = useMutation(lockDashboardApi, {
|
|
||||||
onSuccess: () => {
|
|
||||||
setIsDashboardSlider(false);
|
setIsDashboardSlider(false);
|
||||||
setIsDashboardLocked(true);
|
setIsDashboardLocked(props.lock);
|
||||||
},
|
},
|
||||||
onError: handleError,
|
onError: (error) => {
|
||||||
});
|
showErrorModal(error as APIError);
|
||||||
|
|
||||||
const { mutate: unlockDashboard } = useMutation(unlockDashboardApi, {
|
|
||||||
onSuccess: () => {
|
|
||||||
setIsDashboardLocked(false);
|
|
||||||
},
|
},
|
||||||
onError: handleError,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDashboardLockToggle = async (value: boolean): Promise<void> => {
|
const handleDashboardLockToggle = async (value: boolean): Promise<void> => {
|
||||||
if (selectedDashboard) {
|
if (selectedDashboard) {
|
||||||
if (value) {
|
try {
|
||||||
lockDashboard(selectedDashboard);
|
await lockDashboard({
|
||||||
} else {
|
id: selectedDashboard.id,
|
||||||
unlockDashboard(selectedDashboard);
|
lock: value,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showErrorModal(error as APIError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
import { UseQueryResult } from 'react-query';
|
import { UseQueryResult } from 'react-query';
|
||||||
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
export interface DashboardSortOrder {
|
export interface DashboardSortOrder {
|
||||||
@ -19,7 +20,7 @@ export interface IDashboardContext {
|
|||||||
isDashboardLocked: boolean;
|
isDashboardLocked: boolean;
|
||||||
handleToggleDashboardSlider: (value: boolean) => void;
|
handleToggleDashboardSlider: (value: boolean) => void;
|
||||||
handleDashboardLockToggle: (value: boolean) => void;
|
handleDashboardLockToggle: (value: boolean) => void;
|
||||||
dashboardResponse: UseQueryResult<Dashboard, unknown>;
|
dashboardResponse: UseQueryResult<SuccessResponseV2<Dashboard>, unknown>;
|
||||||
selectedDashboard: Dashboard | undefined;
|
selectedDashboard: Dashboard | undefined;
|
||||||
dashboardId: string;
|
dashboardId: string;
|
||||||
layouts: Layout[];
|
layouts: Layout[];
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Dashboard, DashboardData } from './getAll';
|
import { Dashboard } from './getAll';
|
||||||
|
|
||||||
export type Props =
|
export type Props = {
|
||||||
| {
|
title: Dashboard['data']['title'];
|
||||||
title: Dashboard['data']['title'];
|
uploadedGrafana: boolean;
|
||||||
uploadedGrafana: boolean;
|
version?: string;
|
||||||
version?: string;
|
};
|
||||||
}
|
|
||||||
| { DashboardData: DashboardData; uploadedGrafana: boolean };
|
|
||||||
|
|
||||||
export type PayloadProps = Dashboard;
|
export interface PayloadProps {
|
||||||
|
data: Dashboard;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Dashboard } from './getAll';
|
import { Dashboard } from './getAll';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
uuid: Dashboard['uuid'];
|
id: Dashboard['id'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PayloadProps {
|
export interface PayloadProps {
|
||||||
status: 'success';
|
status: string;
|
||||||
|
data: null;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Dashboard } from './getAll';
|
import { Dashboard } from './getAll';
|
||||||
|
|
||||||
export type Props = {
|
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 { IField } from '../logs/fields';
|
||||||
import { BaseAutocompleteData } from '../queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from '../queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
export type PayloadProps = Dashboard[];
|
|
||||||
|
|
||||||
export const VariableQueryTypeArr = ['QUERY', 'TEXTBOX', 'CUSTOM'] as const;
|
export const VariableQueryTypeArr = ['QUERY', 'TEXTBOX', 'CUSTOM'] as const;
|
||||||
export type TVariableQueryType = typeof VariableQueryTypeArr[number];
|
export type TVariableQueryType = typeof VariableQueryTypeArr[number];
|
||||||
|
|
||||||
@ -50,14 +48,18 @@ export interface IDashboardVariable {
|
|||||||
change?: boolean;
|
change?: boolean;
|
||||||
}
|
}
|
||||||
export interface Dashboard {
|
export interface Dashboard {
|
||||||
id: number;
|
id: string;
|
||||||
uuid: string;
|
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
updatedBy: string;
|
updatedBy: string;
|
||||||
data: DashboardData;
|
data: DashboardData;
|
||||||
isLocked?: boolean;
|
locked?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PayloadProps {
|
||||||
|
data: Dashboard[];
|
||||||
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardTemplate {
|
export interface DashboardTemplate {
|
||||||
@ -69,7 +71,7 @@ export interface DashboardTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardData {
|
export interface DashboardData {
|
||||||
uuid?: string;
|
// uuid?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
name?: 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';
|
import { Dashboard, DashboardData } from './getAll';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
uuid: Dashboard['uuid'];
|
id: Dashboard['id'];
|
||||||
data: DashboardData;
|
data: DashboardData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PayloadProps = Dashboard;
|
export interface PayloadProps {
|
||||||
|
data: Dashboard;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ export enum LicenseEvent {
|
|||||||
export enum LicenseStatus {
|
export enum LicenseStatus {
|
||||||
SUSPENDED = 'SUSPENDED',
|
SUSPENDED = 'SUSPENDED',
|
||||||
VALID = 'VALID',
|
VALID = 'VALID',
|
||||||
|
INVALID = 'INVALID',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LicenseState {
|
export enum LicenseState {
|
||||||
|
2
go.mod
2
go.mod
@ -26,7 +26,6 @@ require (
|
|||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/gosimple/slug v1.10.0
|
|
||||||
github.com/huandu/go-sqlbuilder v1.35.0
|
github.com/huandu/go-sqlbuilder v1.35.0
|
||||||
github.com/jackc/pgx/v5 v5.7.2
|
github.com/jackc/pgx/v5 v5.7.2
|
||||||
github.com/jmoiron/sqlx v1.3.4
|
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/enterprise-certificate-proxy v0.3.4 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
|
||||||
github.com/gopherjs/gopherjs v1.17.2 // 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/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.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/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 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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 h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
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=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
@ -4,25 +4,32 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Module interface {
|
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 {
|
type Handler interface {
|
||||||
|
Create(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
|
Update(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
|
LockUnlock(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
Delete(http.ResponseWriter, *http.Request)
|
Delete(http.ResponseWriter, *http.Request)
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,16 @@ package impldashboard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,8 +23,8 @@ func NewHandler(module dashboard.Module) dashboard.Handler {
|
|||||||
return &handler{module: module}
|
return &handler{module: module}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *handler) Delete(rw http.ResponseWriter, req *http.Request) {
|
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
@ -29,13 +33,151 @@ func (handler *handler) Delete(rw http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
render.Error(rw, err)
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Success(rw, http.StatusOK, nil)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
"github.com/google/uuid"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type module struct {
|
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{
|
return &module{
|
||||||
sqlstore: sqlstore,
|
store: NewStore(sqlstore),
|
||||||
|
settings: scopedProviderSettings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDashboard creates a new dashboard
|
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||||
func (module *module) Create(ctx context.Context, orgID string, email string, data map[string]interface{}) (*types.Dashboard, error) {
|
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, postableDashboard)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dashboards, nil
|
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
err = module.store.Create(ctx, storableDashboard)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -167,28 +43,73 @@ func (module *module) Update(ctx context.Context, orgID, userEmail, uuid string,
|
|||||||
return dashboard, nil
|
return dashboard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (module *module) LockUnlock(ctx context.Context, orgID, uuid string, lock bool) error {
|
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||||
dashboard, err := module.Get(ctx, orgID, uuid)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var lockValue int
|
err = dashboard.LockUnlock(ctx, lock, updatedBy)
|
||||||
if lock {
|
if err != nil {
|
||||||
lockValue = 1
|
return err
|
||||||
} else {
|
}
|
||||||
lockValue = 0
|
storableDashboard, err := dashboardtypes.NewStorableDashboardFromDashboard(dashboard)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = module.
|
err = module.store.Update(ctx, orgID, storableDashboard)
|
||||||
sqlstore.
|
|
||||||
BunDB().
|
|
||||||
NewUpdate().
|
|
||||||
Model(dashboard).
|
|
||||||
Set("locked = ?", lockValue).
|
|
||||||
Where("org_id = ?", orgID).
|
|
||||||
Where("uuid = ?", uuid).
|
|
||||||
Exec(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -196,15 +117,21 @@ func (module *module) LockUnlock(ctx context.Context, orgID, uuid string, lock b
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (module *module) GetByMetricNames(ctx context.Context, orgID string, metricNames []string) (map[string][]map[string]string, error) {
|
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||||
dashboards := []types.Dashboard{}
|
dashboard, err := module.Get(ctx, orgID, id)
|
||||||
err := module.
|
if err != nil {
|
||||||
sqlstore.
|
return err
|
||||||
BunDB().
|
}
|
||||||
NewSelect().
|
|
||||||
Model(&dashboards).
|
if dashboard.Locked {
|
||||||
Where("org_id = ?", orgID).
|
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
|
||||||
Scan(ctx)
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -266,7 +193,7 @@ func (module *module) GetByMetricNames(ctx context.Context, orgID string, metric
|
|||||||
for _, metricName := range metricNames {
|
for _, metricName := range metricNames {
|
||||||
if strings.TrimSpace(key) == metricName {
|
if strings.TrimSpace(key) == metricName {
|
||||||
result[metricName] = append(result[metricName], map[string]string{
|
result[metricName] = append(result[metricName], map[string]string{
|
||||||
"dashboard_id": dashboard.UUID,
|
"dashboard_id": dashboard.ID,
|
||||||
"widget_name": widgetTitle,
|
"widget_name": widgetTitle,
|
||||||
"widget_id": widgetID,
|
"widget_id": widgetID,
|
||||||
"dashboard_name": dashTitle,
|
"dashboard_name": dashTitle,
|
||||||
@ -280,52 +207,3 @@ func (module *module) GetByMetricNames(ctx context.Context, orgID string, metric
|
|||||||
|
|
||||||
return result, nil
|
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
|
||||||
|
}
|
@ -68,6 +68,7 @@ func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
|||||||
err = json.NewDecoder(r.Body).Decode(&req)
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw, err)
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.ID = orgID
|
req.ID = orgID
|
||||||
|
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)
|
||||||
|
}
|
@ -4,11 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||||
|
promValue "github.com/prometheus/prometheus/model/value"
|
||||||
"github.com/prometheus/prometheus/prompb"
|
"github.com/prometheus/prometheus/prompb"
|
||||||
"github.com/prometheus/prometheus/storage"
|
"github.com/prometheus/prometheus/storage"
|
||||||
"github.com/prometheus/prometheus/storage/remote"
|
"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 fingerprint, prevFingerprint uint64
|
||||||
var timestampMs int64
|
var timestampMs int64
|
||||||
var value float64
|
var value float64
|
||||||
|
var flags uint32
|
||||||
|
|
||||||
for rows.Next() {
|
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
|
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
|
// add samples to current time series
|
||||||
ts.Samples = append(ts.Samples, prompb.Sample{
|
ts.Samples = append(ts.Samples, prompb.Sample{
|
||||||
Timestamp: timestampMs,
|
Timestamp: timestampMs,
|
||||||
|
@ -6267,9 +6267,6 @@ func (r *ClickHouseReader) GetUpdatedMetricsMetadata(ctx context.Context, orgID
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
cachedMetadata[metricName] = metadata
|
cachedMetadata[metricName] = metadata
|
||||||
} else {
|
} 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)
|
missingMetrics = append(missingMetrics, metricName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,9 +34,7 @@ type Controller struct {
|
|||||||
serviceConfigRepo ServiceConfigDatabase
|
serviceConfigRepo ServiceConfigDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(sqlStore sqlstore.SQLStore) (
|
func NewController(sqlStore sqlstore.SQLStore) (*Controller, error) {
|
||||||
*Controller, error,
|
|
||||||
) {
|
|
||||||
accountsRepo, err := newCloudProviderAccountsRepository(sqlStore)
|
accountsRepo, err := newCloudProviderAccountsRepository(sqlStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't create cloud provider accounts repo: %w", err)
|
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"`
|
Accounts []types.Account `json:"accounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ListConnectedAccounts(
|
func (c *Controller) ListConnectedAccounts(ctx context.Context, orgId string, cloudProvider string) (
|
||||||
ctx context.Context, orgId string, cloudProvider string,
|
|
||||||
) (
|
|
||||||
*ConnectedAccountsListResponse, *model.ApiError,
|
*ConnectedAccountsListResponse, *model.ApiError,
|
||||||
) {
|
) {
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
@ -103,9 +101,7 @@ type GenerateConnectionUrlResponse struct {
|
|||||||
ConnectionUrl string `json:"connection_url"`
|
ConnectionUrl string `json:"connection_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GenerateConnectionUrl(
|
func (c *Controller) GenerateConnectionUrl(ctx context.Context, orgId string, cloudProvider string, req GenerateConnectionUrlRequest) (*GenerateConnectionUrlResponse, *model.ApiError) {
|
||||||
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.
|
// Account connection with a simple connection URL may not be available for all providers.
|
||||||
if cloudProvider != "aws" {
|
if cloudProvider != "aws" {
|
||||||
return nil, model.BadRequest(fmt.Errorf("unsupported cloud provider: %s", cloudProvider))
|
return nil, model.BadRequest(fmt.Errorf("unsupported cloud provider: %s", cloudProvider))
|
||||||
@ -154,9 +150,7 @@ type AccountStatusResponse struct {
|
|||||||
Status types.AccountStatus `json:"status"`
|
Status types.AccountStatus `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GetAccountStatus(
|
func (c *Controller) GetAccountStatus(ctx context.Context, orgId string, cloudProvider string, accountId string) (
|
||||||
ctx context.Context, orgId string, cloudProvider string, accountId string,
|
|
||||||
) (
|
|
||||||
*AccountStatusResponse, *model.ApiError,
|
*AccountStatusResponse, *model.ApiError,
|
||||||
) {
|
) {
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
@ -198,9 +192,7 @@ type IntegrationConfigForAgent struct {
|
|||||||
TelemetryCollectionStrategy *CompiledCollectionStrategy `json:"telemetry,omitempty"`
|
TelemetryCollectionStrategy *CompiledCollectionStrategy `json:"telemetry,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) CheckInAsAgent(
|
func (c *Controller) CheckInAsAgent(ctx context.Context, orgId string, cloudProvider string, req AgentCheckInRequest) (*AgentCheckInResponse, error) {
|
||||||
ctx context.Context, orgId string, cloudProvider string, req AgentCheckInRequest,
|
|
||||||
) (*AgentCheckInResponse, error) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
@ -293,13 +285,7 @@ type UpdateAccountConfigRequest struct {
|
|||||||
Config types.AccountConfig `json:"config"`
|
Config types.AccountConfig `json:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) UpdateAccountConfig(
|
func (c *Controller) UpdateAccountConfig(ctx context.Context, orgId string, cloudProvider string, accountId string, req UpdateAccountConfigRequest) (*types.Account, *model.ApiError) {
|
||||||
ctx context.Context,
|
|
||||||
orgId string,
|
|
||||||
cloudProvider string,
|
|
||||||
accountId string,
|
|
||||||
req UpdateAccountConfigRequest,
|
|
||||||
) (*types.Account, *model.ApiError) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
@ -316,9 +302,7 @@ func (c *Controller) UpdateAccountConfig(
|
|||||||
return &account, nil
|
return &account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) DisconnectAccount(
|
func (c *Controller) DisconnectAccount(ctx context.Context, orgId string, cloudProvider string, accountId string) (*types.CloudIntegration, *model.ApiError) {
|
||||||
ctx context.Context, orgId string, cloudProvider string, accountId string,
|
|
||||||
) (*types.CloudIntegration, *model.ApiError) {
|
|
||||||
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
@ -520,10 +504,8 @@ func (c *Controller) UpdateServiceConfig(
|
|||||||
|
|
||||||
// All dashboards that are available based on cloud integrations configuration
|
// All dashboards that are available based on cloud integrations configuration
|
||||||
// across all cloud providers
|
// across all cloud providers
|
||||||
func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) (
|
func (c *Controller) AvailableDashboards(ctx context.Context, orgId valuer.UUID) ([]*dashboardtypes.Dashboard, *model.ApiError) {
|
||||||
[]*types.Dashboard, *model.ApiError,
|
allDashboards := []*dashboardtypes.Dashboard{}
|
||||||
) {
|
|
||||||
allDashboards := []*types.Dashboard{}
|
|
||||||
|
|
||||||
for _, provider := range []string{"aws"} {
|
for _, provider := range []string{"aws"} {
|
||||||
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, provider)
|
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, provider)
|
||||||
@ -539,11 +521,8 @@ func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) (
|
|||||||
return allDashboards, nil
|
return allDashboards, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) AvailableDashboardsForCloudProvider(
|
func (c *Controller) AvailableDashboardsForCloudProvider(ctx context.Context, orgID valuer.UUID, cloudProvider string) ([]*dashboardtypes.Dashboard, *model.ApiError) {
|
||||||
ctx context.Context, orgID string, cloudProvider string,
|
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgID.StringValue(), cloudProvider)
|
||||||
) ([]*types.Dashboard, *model.ApiError) {
|
|
||||||
|
|
||||||
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgID, cloudProvider)
|
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, model.WrapApiError(apiErr, "couldn't list connected cloud accounts")
|
return nil, model.WrapApiError(apiErr, "couldn't list connected cloud accounts")
|
||||||
}
|
}
|
||||||
@ -554,7 +533,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
|||||||
for _, ar := range accountRecords {
|
for _, ar := range accountRecords {
|
||||||
if ar.AccountID != nil {
|
if ar.AccountID != nil {
|
||||||
configsBySvcId, apiErr := c.serviceConfigRepo.getAllForAccount(
|
configsBySvcId, apiErr := c.serviceConfigRepo.getAllForAccount(
|
||||||
ctx, orgID, ar.ID.StringValue(),
|
ctx, orgID.StringValue(), ar.ID.StringValue(),
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
@ -573,16 +552,15 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
svcDashboards := []*types.Dashboard{}
|
svcDashboards := []*dashboardtypes.Dashboard{}
|
||||||
for _, svc := range allServices {
|
for _, svc := range allServices {
|
||||||
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
|
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
|
||||||
if serviceDashboardsCreatedAt != nil {
|
if serviceDashboardsCreatedAt != nil {
|
||||||
for _, d := range svc.Assets.Dashboards {
|
for _, d := range svc.Assets.Dashboards {
|
||||||
isLocked := 1
|
|
||||||
author := fmt.Sprintf("%s-integration", cloudProvider)
|
author := fmt.Sprintf("%s-integration", cloudProvider)
|
||||||
svcDashboards = append(svcDashboards, &types.Dashboard{
|
svcDashboards = append(svcDashboards, &dashboardtypes.Dashboard{
|
||||||
UUID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
ID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
||||||
Locked: &isLocked,
|
Locked: true,
|
||||||
Data: *d.Definition,
|
Data: *d.Definition,
|
||||||
TimeAuditable: types.TimeAuditable{
|
TimeAuditable: types.TimeAuditable{
|
||||||
CreatedAt: *serviceDashboardsCreatedAt,
|
CreatedAt: *serviceDashboardsCreatedAt,
|
||||||
@ -592,6 +570,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
|||||||
CreatedBy: author,
|
CreatedBy: author,
|
||||||
UpdatedBy: author,
|
UpdatedBy: author,
|
||||||
},
|
},
|
||||||
|
OrgID: orgID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
servicesWithAvailableMetrics[svc.Id] = nil
|
servicesWithAvailableMetrics[svc.Id] = nil
|
||||||
@ -600,11 +579,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
|||||||
|
|
||||||
return svcDashboards, nil
|
return svcDashboards, nil
|
||||||
}
|
}
|
||||||
func (c *Controller) GetDashboardById(
|
func (c *Controller) GetDashboardById(ctx context.Context, orgId valuer.UUID, dashboardUuid string) (*dashboardtypes.Dashboard, *model.ApiError) {
|
||||||
ctx context.Context,
|
|
||||||
orgId string,
|
|
||||||
dashboardUuid string,
|
|
||||||
) (*types.Dashboard, *model.ApiError) {
|
|
||||||
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
|
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
@ -612,38 +587,28 @@ func (c *Controller) GetDashboardById(
|
|||||||
|
|
||||||
allDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, cloudProvider)
|
allDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, cloudProvider)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, model.WrapApiError(
|
return nil, model.WrapApiError(apiErr, "couldn't list available dashboards")
|
||||||
apiErr, fmt.Sprintf("couldn't list available dashboards"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range allDashboards {
|
for _, d := range allDashboards {
|
||||||
if d.UUID == dashboardUuid {
|
if d.ID == dashboardUuid {
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, model.NotFoundError(fmt.Errorf(
|
return nil, model.NotFoundError(fmt.Errorf("couldn't find dashboard with uuid: %s", dashboardUuid))
|
||||||
"couldn't find dashboard with uuid: %s", dashboardUuid,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) dashboardUuid(
|
func (c *Controller) dashboardUuid(
|
||||||
cloudProvider string, svcId string, dashboardId string,
|
cloudProvider string, svcId string, dashboardId string,
|
||||||
) string {
|
) string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf("cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId)
|
||||||
"cloud-integration--%s--%s--%s", cloudProvider, svcId, dashboardId,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) parseDashboardUuid(dashboardUuid string) (
|
func (c *Controller) parseDashboardUuid(dashboardUuid string) (cloudProvider string, svcId string, dashboardId string, apiErr *model.ApiError) {
|
||||||
cloudProvider string, svcId string, dashboardId string, apiErr *model.ApiError,
|
|
||||||
) {
|
|
||||||
parts := strings.SplitN(dashboardUuid, "--", 4)
|
parts := strings.SplitN(dashboardUuid, "--", 4)
|
||||||
if len(parts) != 4 || parts[0] != "cloud-integration" {
|
if len(parts) != 4 || parts[0] != "cloud-integration" {
|
||||||
return "", "", "", model.BadRequest(fmt.Errorf(
|
return "", "", "", model.BadRequest(fmt.Errorf("invalid cloud integration dashboard id"))
|
||||||
"invalid cloud integration dashboard id",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts[1], parts[2], parts[3], nil
|
return parts[1], parts[2], parts[3], nil
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,851 @@
|
|||||||
|
{
|
||||||
|
"description": "View key AWS ECS metrics with an out of the box dashboard.\n",
|
||||||
|
"image":"data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3Csvg%20width%3D%2280px%22%20height%3D%2280px%22%20viewBox%3D%220%200%2080%2080%22%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3C!--%20Generator%3A%20Sketch%2064%20(93537)%20-%20https%3A%2F%2Fsketch.com%20--%3E%3Ctitle%3EIcon-Architecture%2F64%2FArch_Amazon-Elastic-Container-Service_64%3C%2Ftitle%3E%3Cdesc%3ECreated%20with%20Sketch.%3C%2Fdesc%3E%3Cdefs%3E%3ClinearGradient%20x1%3D%220%25%22%20y1%3D%22100%25%22%20x2%3D%22100%25%22%20y2%3D%220%25%22%20id%3D%22linearGradient-1%22%3E%3Cstop%20stop-color%3D%22%23C8511B%22%20offset%3D%220%25%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23FF9900%22%20offset%3D%22100%25%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Cg%20id%3D%22Icon-Architecture%2F64%2FArch_Amazon-Elastic-Container-Service_64%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cg%20id%3D%22Icon-Architecture-BG%2F64%2FContainers%22%20fill%3D%22url(%23linearGradient-1)%22%3E%3Crect%20id%3D%22Rectangle%22%20x%3D%220%22%20y%3D%220%22%20width%3D%2280%22%20height%3D%2280%22%3E%3C%2Frect%3E%3C%2Fg%3E%3Cpath%20d%3D%22M64%2C48.2340095%20L56%2C43.4330117%20L56%2C32.0000169%20C56%2C31.6440171%2055.812%2C31.3150172%2055.504%2C31.1360173%20L44%2C24.4260204%20L44%2C14.7520248%20L64%2C26.5710194%20L64%2C48.2340095%20Z%20M65.509%2C25.13902%20L43.509%2C12.139026%20C43.199%2C11.9560261%2042.818%2C11.9540261%2042.504%2C12.131026%20C42.193%2C12.3090259%2042%2C12.6410257%2042%2C13.0000256%20L42%2C25.0000201%20C42%2C25.3550199%2042.189%2C25.6840198%2042.496%2C25.8640197%20L54%2C32.5740166%20L54%2C44.0000114%20C54%2C44.3510113%2054.185%2C44.6770111%2054.486%2C44.857011%20L64.486%2C50.8570083%20C64.644%2C50.9520082%2064.822%2C51%2065%2C51%20C65.17%2C51%2065.34%2C50.9570082%2065.493%2C50.8700083%20C65.807%2C50.6930084%2066%2C50.3600085%2066%2C50%20L66%2C26.0000196%20C66%2C25.6460198%2065.814%2C25.31902%2065.509%2C25.13902%20L65.509%2C25.13902%20Z%20M40.445%2C66.863001%20L17%2C54.3990067%20L17%2C26.5710194%20L37%2C14.7520248%20L37%2C24.4510204%20L26.463%2C31.1560173%20C26.175%2C31.3400172%2026%2C31.6580171%2026%2C32.0000169%20L26%2C49.0000091%20C26%2C49.373009%2026.208%2C49.7150088%2026.538%2C49.8870087%20L39.991%2C56.8870055%20C40.28%2C57.0370055%2040.624%2C57.0380055%2040.912%2C56.8880055%20L53.964%2C50.1440086%20L61.996%2C54.9640064%20L40.445%2C66.863001%20Z%20M64.515%2C54.1420068%20L54.515%2C48.1420095%20C54.217%2C47.9640096%2053.849%2C47.9520096%2053.541%2C48.1120095%20L40.455%2C54.8730065%20L28%2C48.3930094%20L28%2C32.5490167%20L38.537%2C25.8440197%20C38.825%2C25.6600198%2039%2C25.3420199%2039%2C25.0000201%20L39%2C13.0000256%20C39%2C12.6410257%2038.808%2C12.3090259%2038.496%2C12.131026%20C38.184%2C11.9540261%2037.802%2C11.9560261%2037.491%2C12.139026%20L15.491%2C25.13902%20C15.187%2C25.31902%2015%2C25.6460198%2015%2C26.0000196%20L15%2C55%20C15%2C55.3690062%2015.204%2C55.7090061%2015.53%2C55.883006%20L39.984%2C68.8830001%20C40.131%2C68.961%2040.292%2C69%2040.453%2C69%20C40.62%2C69%2040.786%2C68.958%2040.937%2C68.8750001%20L64.484%2C55.875006%20C64.797%2C55.7020061%2064.993%2C55.3750062%2065.0001416%2C55.0180064%20C65.006%2C54.6600066%2064.821%2C54.3260067%2064.515%2C54.1420068%20L64.515%2C54.1420068%20Z%22%20id%3D%22Amazon-Elastic-Container-Service_Icon_64_Squid%22%20fill%3D%22%23FFFFFF%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E",
|
||||||
|
"layout": [
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "f78becf8-0328-48b4-84b6-ff4dac325940",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "2b4eac06-b426-4f78-b874-2e1734c4104b",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "5bea2bc0-13a2-4937-bccb-60ffe8a43ad5",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "6fac67b0-50ec-4b43-ac4b-320a303d0369",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"panelMap": {},
|
||||||
|
"tags": [],
|
||||||
|
"title": "AWS ECS Overview",
|
||||||
|
"uploadedGrafana": false,
|
||||||
|
"variables": {
|
||||||
|
"51f4fa2b-89c7-47c2-9795-f32cffaab985": {
|
||||||
|
"allSelected": false,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "AWS Account ID",
|
||||||
|
"id": "51f4fa2b-89c7-47c2-9795-f32cffaab985",
|
||||||
|
"key": "51f4fa2b-89c7-47c2-9795-f32cffaab985",
|
||||||
|
"modificationUUID": "7b814d17-8fff-4ed6-a4ea-90e3b1a97584",
|
||||||
|
"multiSelect": false,
|
||||||
|
"name": "Account",
|
||||||
|
"order": 0,
|
||||||
|
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'cloud.account.id') AS `cloud.account.id`\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'aws_ECS_MemoryUtilization_max' GROUP BY `cloud.account.id`",
|
||||||
|
"showALLOption": false,
|
||||||
|
"sort": "DISABLED",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
},
|
||||||
|
"9faf0f4b-b245-4b3c-83a3-60cfa76dfeb0": {
|
||||||
|
"allSelected": false,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "Account Region",
|
||||||
|
"id": "9faf0f4b-b245-4b3c-83a3-60cfa76dfeb0",
|
||||||
|
"key": "9faf0f4b-b245-4b3c-83a3-60cfa76dfeb0",
|
||||||
|
"modificationUUID": "3b5f499b-22a3-4c8a-847c-8d3811c9e6b2",
|
||||||
|
"multiSelect": false,
|
||||||
|
"name": "Region",
|
||||||
|
"order": 1,
|
||||||
|
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'cloud.region') AS region\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'aws_ECS_MemoryUtilization_max' AND JSONExtractString(labels, 'cloud.account.id') IN {{.Account}} GROUP BY region",
|
||||||
|
"showALLOption": false,
|
||||||
|
"sort": "ASC",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
},
|
||||||
|
"bfbdbcbe-a168-4d81-b108-36339e249116": {
|
||||||
|
"allSelected": true,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "ECS Cluster Name",
|
||||||
|
"id": "bfbdbcbe-a168-4d81-b108-36339e249116",
|
||||||
|
"key": "bfbdbcbe-a168-4d81-b108-36339e249116",
|
||||||
|
"modificationUUID": "9fb0d63c-ac6c-497d-82b3-17d95944e245",
|
||||||
|
"multiSelect": true,
|
||||||
|
"name": "Cluster",
|
||||||
|
"order": 2,
|
||||||
|
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'ClusterName') AS cluster\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'aws_ECS_MemoryUtilization_max' AND JSONExtractString(labels, 'cloud.account.id') IN {{.Account}} AND JSONExtractString(labels, 'cloud.region') IN {{.Region}}\nGROUP BY cluster",
|
||||||
|
"showALLOption": true,
|
||||||
|
"sort": "ASC",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "v4",
|
||||||
|
"widgets": [
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "f78becf8-0328-48b4-84b6-ff4dac325940",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_ECS_MemoryUtilization_max--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_ECS_MemoryUtilization_max",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "26ac617d",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57172ed9",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "49b9f85e",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Cluster"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ServiceName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ServiceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{ServiceName}} ({{ClusterName}})",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "max",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "max"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "56068fdd-d523-4117-92fa-87c6518ad07c",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Maximum Memory Utilization",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "2b4eac06-b426-4f78-b874-2e1734c4104b",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_ECS_MemoryUtilization_min--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_ECS_MemoryUtilization_min",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "min",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "cd4b8848",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aa5115c6",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f60677b6",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Cluster"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ServiceName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ServiceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{ServiceName}} ({{ClusterName}})",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "min",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "min"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "fb19342e-cbde-40d8-b12f-ad108698356b",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Minimum Memory Utilization",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "5bea2bc0-13a2-4937-bccb-60ffe8a43ad5",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_ECS_CPUUtilization_max--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_ECS_CPUUtilization_max",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "2c13c8ee",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f489f6a8",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "94012320",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Cluster"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ServiceName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ServiceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{ServiceName}} ({{ClusterName}})",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "max",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "max"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "273e0a76-c780-4b9a-9b03-2649d4227173",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Maximum CPU Utilization",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "6fac67b0-50ec-4b43-ac4b-320a303d0369",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_ECS_CPUUtilization_min--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_ECS_CPUUtilization_min",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "min",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "758ba906",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4ffe6bf7",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53d98059",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Cluster"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ServiceName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ServiceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "ClusterName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "ClusterName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{ServiceName}} ({{ClusterName}})",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "min",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "min"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "c89482b3-5a98-4e2c-be0d-ef036d7dac05",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Minimum CPU Utilization",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,818 @@
|
|||||||
|
{
|
||||||
|
"description": "View key AWS SNS metrics with an out of the box dashboard.",
|
||||||
|
"image": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODBweCIgaGVpZ2h0PSI4MHB4IiB2aWV3Qm94PSIwIDAgODAgODAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDY0ICg5MzUzNykgLSBodHRwczovL3NrZXRjaC5jb20gLS0+CiAgICA8dGl0bGU+SWNvbi1BcmNoaXRlY3R1cmUvNjQvQXJjaF9BV1MtU2ltcGxlLU5vdGlmaWNhdGlvbi1TZXJ2aWNlXzY0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+CiAgICAgICAgPGxpbmVhckdyYWRpZW50IHgxPSIwJSIgeTE9IjEwMCUiIHgyPSIxMDAlIiB5Mj0iMCUiIGlkPSJsaW5lYXJHcmFkaWVudC0xIj4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iI0IwMDg0RCIgb2Zmc2V0PSIwJSI+PC9zdG9wPgogICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjRkY0RjhCIiBvZmZzZXQ9IjEwMCUiPjwvc3RvcD4KICAgICAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPC9kZWZzPgogICAgPGcgaWQ9Ikljb24tQXJjaGl0ZWN0dXJlLzY0L0FyY2hfQVdTLVNpbXBsZS1Ob3RpZmljYXRpb24tU2VydmljZV82NCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9Ikljb24tQXJjaGl0ZWN0dXJlLUJHLzY0L0FwcGxpY2F0aW9uLUludGVncmF0aW9uIiBmaWxsPSJ1cmwoI2xpbmVhckdyYWRpZW50LTEpIj4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZSIgeD0iMCIgeT0iMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIj48L3JlY3Q+CiAgICAgICAgPC9nPgogICAgICAgIDxwYXRoIGQ9Ik0xNywzOCBDMTguMTAzLDM4IDE5LDM4Ljg5NyAxOSw0MCBDMTksNDEuMTAzIDE4LjEwMyw0MiAxNyw0MiBDMTUuODk3LDQyIDE1LDQxLjEwMyAxNSw0MCBDMTUsMzguODk3IDE1Ljg5NywzOCAxNywzOCBMMTcsMzggWiBNNDEsNjQgQzI5LjMxNCw2NCAxOS4yODksNTUuNDY2IDE3LjE5NCw0My45OCBDMTguOTY1LDQzLjg5NCAyMC40MjcsNDIuNjU5IDIwLjg1Nyw0MSBMMjcsNDEgTDI3LDM5IEwyMC44NTcsMzkgQzIwLjQyNywzNy4zNDIgMTguOTY2LDM2LjEwNyAxNy4xOTUsMzYuMDIgQzE5LjI4NSwyNC43MSAyOS41MTEsMTYgNDEsMTYgQzQ1LjMxMywxNiA0OS44MzIsMTcuNjIyIDU0LjQyOSwyMC44MjEgTDU1LjU3MSwxOS4xNzkgQzUwLjYzMywxNS43NDMgNDUuNzMsMTQgNDEsMTQgQzI4LjI3LDE0IDE2Ljk0OSwyMy44NjUgMTUuMDYzLDM2LjUyMSBDMTMuODM5LDM3LjIwNyAxMywzOC41IDEzLDQwIEMxMyw0MS41IDEzLjgzOSw0Mi43OTMgMTUuMDYzLDQzLjQ3OCBDMTYuOTcsNTYuMzQxIDI4LjA1Niw2NiA0MSw2NiBDNDYuNDA3LDY2IDUxLjk0Miw2NC4xNTcgNTYuNTg1LDYwLjgxMSBMNTUuNDE1LDU5LjE4OSBDNTEuMTEsNjIuMjkyIDQ1Ljk5MSw2NCA0MSw2NCBMNDEsNjQgWiBNMzAuMTAxLDM2LjQ0MiBDMzEuOTU1LDM2Ljg5NSAzNC4yNzUsMzcgMzYsMzcgQzM3LjY0MiwzNyAzOS44MjMsMzYuOTA1IDQxLjYyOSwzNi41MDYgTDM3LjEwNSw0NS41NTMgQzM3LjAzNiw0NS42OTEgMzcsNDUuODQ1IDM3LDQ2IEwzNyw1MC40NTMgQzM2LjE5OSw1MC45NjQgMzQuODMzLDUxLjgxMiAzNCw1MS45ODYgTDM0LDQ2IEMzNCw0NS44NjggMzMuOTc0LDQ1LjczNyAzMy45MjMsNDUuNjE1IEwzMC4xMDEsMzYuNDQyIFogTTM2LDMzIEM0MC4wMjUsMzMgNDIuMTc0LDMzLjYwNCA0Mi44NDEsMzQgQzQyLjE3NCwzNC4zOTYgNDAuMDI1LDM1IDM2LDM1IEMzMS45NzUsMzUgMjkuODI2LDM0LjM5NiAyOS4xNTksMzQgQzI5LjgyNiwzMy42MDQgMzEuOTc1LDMzIDM2LDMzIEwzNiwzMyBaIE0zMyw1NCBMMzQsNTQgQzM0LjA0Myw1NCAzNC4wODYsNTMuOTk3IDM0LjEyOCw1My45OTIgQzM1LjM1Miw1My44MzMgMzYuOTA5LDUyLjg4NyAzOC4yNzIsNTIuMDEzIEwzOC41MzUsNTEuODQ1IEMzOC44MjQsNTEuNjYxIDM5LDUxLjM0MiAzOSw1MSBMMzksNDYuMjM2IEw0NC41NTksMzUuMTIgQzQ0LjgzMywzNC44MDEgNDUsMzQuNDM0IDQ1LDM0IEM0NSwzMS4zOSAzOS4zNjEsMzEgMzYsMzEgQzMyLjYzOSwzMSAyNywzMS4zOSAyNywzNCBDMjcsMzQuMzY2IDI3LjEyLDM0LjY4NCAyNy4zMiwzNC45NjcgTDMyLDQ2LjIgTDMyLDUzIEMzMiw1My41NTIgMzIuNDQ3LDU0IDMzLDU0IEwzMyw1NCBaIE02Miw1MyBDNjMuMTAzLDUzIDY0LDUzLjg5NyA2NCw1NSBDNjQsNTYuMTAzIDYzLjEwMyw1NyA2Miw1NyBDNjAuODk3LDU3IDYwLDU2LjEwMyA2MCw1NSBDNjAsNTMuODk3IDYwLjg5Nyw1MyA2Miw1MyBMNjIsNTMgWiBNNjIsMjMgQzYzLjEwMywyMyA2NCwyMy44OTcgNjQsMjUgQzY0LDI2LjEwMyA2My4xMDMsMjcgNjIsMjcgQzYwLjg5NywyNyA2MCwyNi4xMDMgNjAsMjUgQzYwLDIzLjg5NyA2MC44OTcsMjMgNjIsMjMgTDYyLDIzIFogTTY0LDM4IEM2NS4xMDMsMzggNjYsMzguODk3IDY2LDQwIEM2Niw0MS4xMDMgNjUuMTAzLDQyIDY0LDQyIEM2Mi44OTcsNDIgNjIsNDEuMTAzIDYyLDQwIEM2MiwzOC44OTcgNjIuODk3LDM4IDY0LDM4IEw2NCwzOCBaIE01NCw0MSBMNjAuMTQzLDQxIEM2MC41ODksNDIuNzIgNjIuMTQyLDQ0IDY0LDQ0IEM2Ni4yMDYsNDQgNjgsNDIuMjA2IDY4LDQwIEM2OCwzNy43OTQgNjYuMjA2LDM2IDY0LDM2IEM2Mi4xNDIsMzYgNjAuNTg5LDM3LjI4IDYwLjE0MywzOSBMNTQsMzkgTDU0LDI2IEw1OC4xNDMsMjYgQzU4LjU4OSwyNy43MiA2MC4xNDIsMjkgNjIsMjkgQzY0LjIwNiwyOSA2NiwyNy4yMDYgNjYsMjUgQzY2LDIyLjc5NCA2NC4yMDYsMjEgNjIsMjEgQzYwLjE0MiwyMSA1OC41ODksMjIuMjggNTguMTQzLDI0IEw1MywyNCBDNTIuNDQ3LDI0IDUyLDI0LjQ0OCA1MiwyNSBMNTIsMzkgTDQ1LDM5IEw0NSw0MSBMNTIsNDEgTDUyLDU1IEM1Miw1NS41NTIgNTIuNDQ3LDU2IDUzLDU2IEw1OC4xNDMsNTYgQzU4LjU4OSw1Ny43MiA2MC4xNDIsNTkgNjIsNTkgQzY0LjIwNiw1OSA2Niw1Ny4yMDYgNjYsNTUgQzY2LDUyLjc5NCA2NC4yMDYsNTEgNjIsNTEgQzYwLjE0Miw1MSA1OC41ODksNTIuMjggNTguMTQzLDU0IEw1NCw1NCBMNTQsNDEgWiIgaWQ9IkFXUy1TaW1wbGUtTm90aWZpY2F0aW9uLVNlcnZpY2VfSWNvbl82NF9TcXVpZCIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgPC9nPgo8L3N2Zz4=",
|
||||||
|
"layout": [
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "4eb87f89-0213-4773-9b06-6aecc6701898",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "7a010b4e-ea7c-4a45-a9eb-93af650c45b4",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "2299d4e3-6c40-4bf2-a550-c7bb8a7acd38",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 6,
|
||||||
|
"i": "16eec8b7-de1a-4039-b180-24c7a6704b6e",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"panelMap": {},
|
||||||
|
"tags": [],
|
||||||
|
"title": "SNS Overview",
|
||||||
|
"uploadedGrafana": false,
|
||||||
|
"variables": {
|
||||||
|
"51f4fa2b-89c7-47c2-9795-f32cffaab985": {
|
||||||
|
"allSelected": false,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "AWS Account ID",
|
||||||
|
"id": "51f4fa2b-89c7-47c2-9795-f32cffaab985",
|
||||||
|
"key": "51f4fa2b-89c7-47c2-9795-f32cffaab985",
|
||||||
|
"modificationUUID": "b7a6b06b-fa1f-4fb8-b70e-6bd9b350f29e",
|
||||||
|
"multiSelect": false,
|
||||||
|
"name": "Account",
|
||||||
|
"order": 0,
|
||||||
|
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'cloud.account.id') AS `cloud.account.id`\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'aws_SNS_PublishSize_count' GROUP BY `cloud.account.id`",
|
||||||
|
"showALLOption": false,
|
||||||
|
"sort": "DISABLED",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
},
|
||||||
|
"9faf0f4b-b245-4b3c-83a3-60cfa76dfeb0": {
|
||||||
|
"allSelected": false,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "Account Region",
|
||||||
|
"id": "9faf0f4b-b245-4b3c-83a3-60cfa76dfeb0",
|
||||||
|
"key": "9faf0f4b-b245-4b3c-83a3-60cfa76dfeb0",
|
||||||
|
"modificationUUID": "8428a5de-bfd1-4a69-9601-63e3041cd556",
|
||||||
|
"multiSelect": false,
|
||||||
|
"name": "Region",
|
||||||
|
"order": 1,
|
||||||
|
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'cloud.region') AS region\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'aws_SNS_PublishSize_count' AND JSONExtractString(labels, 'cloud.account.id') IN {{.Account}} GROUP BY region",
|
||||||
|
"showALLOption": false,
|
||||||
|
"sort": "ASC",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
},
|
||||||
|
"bfbdbcbe-a168-4d81-b108-36339e249116": {
|
||||||
|
"allSelected": true,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "SNS Topic Name",
|
||||||
|
"id": "bfbdbcbe-a168-4d81-b108-36339e249116",
|
||||||
|
"modificationUUID": "dfed7272-16dc-4eb6-99bf-7c82fc8e04f0",
|
||||||
|
"multiSelect": true,
|
||||||
|
"name": "Topic",
|
||||||
|
"order": 2,
|
||||||
|
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'TopicName') AS topic\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'aws_SNS_PublishSize_count' AND JSONExtractString(labels, 'cloud.account.id') IN {{.Account}} AND JSONExtractString(labels, 'cloud.region') IN {{.Region}}\nGROUP BY topic",
|
||||||
|
"showALLOption": true,
|
||||||
|
"sort": "ASC",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "v4",
|
||||||
|
"widgets": [
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "4eb87f89-0213-4773-9b06-6aecc6701898",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_SNS_NumberOfMessagesPublished_max--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_SNS_NumberOfMessagesPublished_max",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "8fd51b53",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Topic"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b18187c3",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eebe4578",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{TopicName}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "max",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "max"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "9c67615a-55f7-42da-835c-86922f2ff8bb",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Number of Messages Published",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "7a010b4e-ea7c-4a45-a9eb-93af650c45b4",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_SNS_PublishSize_max--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_SNS_PublishSize_max",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "1aa0d1a9",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Topic"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "62255cff",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "17c7153e",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{TopicName}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "max",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "max"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "a635a15b-dfe6-4617-a82e-29d93e27deaf",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Published Message Size",
|
||||||
|
"yAxisUnit": "decbytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "2299d4e3-6c40-4bf2-a550-c7bb8a7acd38",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_SNS_NumberOfNotificationsDelivered_max--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_SNS_NumberOfNotificationsDelivered_max",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "c96a4ac0",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Topic"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8ca86829",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8a444f66",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{TopicName}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "max",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "max"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "0d2fc26c-9b21-4dfc-b631-64b7c8d3bd71",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Number of Notifications Delivered",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucketCount": 30,
|
||||||
|
"bucketWidth": 0,
|
||||||
|
"columnUnits": {},
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "16eec8b7-de1a-4039-b180-24c7a6704b6e",
|
||||||
|
"isLogScale": false,
|
||||||
|
"isStacked": false,
|
||||||
|
"mergeAllActiveQueries": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "aws_SNS_NumberOfNotificationsFailed_max--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "aws_SNS_NumberOfNotificationsFailed_max",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "6175f3d5",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"$Topic"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e2084931",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.region--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.region",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0b05209a",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "cloud.account.id--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "cloud.account.id",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "$Account"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"functions": [],
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "TopicName--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "TopicName",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{TopicName}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "avg",
|
||||||
|
"spaceAggregation": "max",
|
||||||
|
"stepInterval": 60,
|
||||||
|
"timeAggregation": "max"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "526247af-6ac9-42ff-83e9-cce0e32a9e63",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"selectedLogFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "body",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selectedTracesFields": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "serviceName--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "serviceName",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "name--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "durationNano--float64--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "durationNano",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "httpMethod--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "httpMethod",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "responseStatusCode--string--tag--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "responseStatusCode",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"softMax": 0,
|
||||||
|
"softMin": 0,
|
||||||
|
"stackedBarChart": false,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Number of Notifications Failed",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
@ -82,10 +82,10 @@ type AWSLogsStrategy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Dashboard struct {
|
type Dashboard struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
Definition *types.DashboardData `json:"definition,omitempty"`
|
Definition *dashboardtypes.StorableDashboardData `json:"definition,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -21,6 +20,8 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||||
"github.com/SigNoz/signoz/pkg/apis/fields"
|
"github.com/SigNoz/signoz/pkg/apis/fields"
|
||||||
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
|
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
|
||||||
@ -60,6 +61,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||||
@ -512,11 +514,12 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.editDowntimeSchedule)).Methods(http.MethodPut)
|
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.editDowntimeSchedule)).Methods(http.MethodPut)
|
||||||
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.deleteDowntimeSchedule)).Methods(http.MethodDelete)
|
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.deleteDowntimeSchedule)).Methods(http.MethodDelete)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/dashboards", am.ViewAccess(aH.getDashboards)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/dashboards", am.ViewAccess(aH.List)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/dashboards", am.EditAccess(aH.createDashboards)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/dashboards", am.EditAccess(aH.Signoz.Handlers.Dashboard.Create)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/dashboards/{uuid}", am.ViewAccess(aH.getDashboard)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/dashboards/{id}", am.ViewAccess(aH.Get)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/dashboards/{uuid}", am.EditAccess(aH.updateDashboard)).Methods(http.MethodPut)
|
router.HandleFunc("/api/v1/dashboards/{id}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Update)).Methods(http.MethodPut)
|
||||||
router.HandleFunc("/api/v1/dashboards/{uuid}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Delete)).Methods(http.MethodDelete)
|
router.HandleFunc("/api/v1/dashboards/{id}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Delete)).Methods(http.MethodDelete)
|
||||||
|
router.HandleFunc("/api/v1/dashboards/{id}/lock", am.EditAccess(aH.Signoz.Handlers.Dashboard.LockUnlock)).Methods(http.MethodPut)
|
||||||
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.Signoz.Handlers.SavedView.List)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.Signoz.Handlers.SavedView.List)).Methods(http.MethodGet)
|
||||||
@ -528,7 +531,6 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
router.HandleFunc("/api/v1/feedback", am.OpenAccess(aH.submitFeedback)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/feedback", am.OpenAccess(aH.submitFeedback)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/event", am.ViewAccess(aH.registerEvent)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/event", am.ViewAccess(aH.registerEvent)).Methods(http.MethodPost)
|
||||||
|
|
||||||
// router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet)
|
|
||||||
router.HandleFunc("/api/v1/services", am.ViewAccess(aH.getServices)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/services", am.ViewAccess(aH.getServices)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/services/list", am.ViewAccess(aH.getServicesList)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/services/list", am.ViewAccess(aH.getServicesList)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/service/top_operations", am.ViewAccess(aH.getTopOperations)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/service/top_operations", am.ViewAccess(aH.getTopOperations)).Methods(http.MethodPost)
|
||||||
@ -1084,77 +1086,6 @@ func (aH *APIHandler) listRules(w http.ResponseWriter, r *http.Request) {
|
|||||||
aH.Respond(w, rules)
|
aH.Respond(w, rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
allDashboards, errv2 := aH.Signoz.Modules.Dashboard.List(r.Context(), claims.OrgID)
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ic := aH.IntegrationsController
|
|
||||||
installedIntegrationDashboards, err := ic.GetDashboardsForInstalledIntegrations(r.Context(), claims.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("failed to get dashboards for installed integrations", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
allDashboards = append(allDashboards, installedIntegrationDashboards...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudIntegrationDashboards, err := aH.CloudIntegrationsController.AvailableDashboards(r.Context(), claims.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("failed to get cloud dashboards", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
allDashboards = append(allDashboards, cloudIntegrationDashboards...)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagsFromReq, ok := r.URL.Query()["tags"]
|
|
||||||
if !ok || len(tagsFromReq) == 0 || tagsFromReq[0] == "" {
|
|
||||||
aH.Respond(w, allDashboards)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tags2Dash := make(map[string][]int)
|
|
||||||
for i := 0; i < len(allDashboards); i++ {
|
|
||||||
tags, ok := (allDashboards)[i].Data["tags"].([]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tagsArray := make([]string, len(tags))
|
|
||||||
for i, v := range tags {
|
|
||||||
tagsArray[i] = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range tagsArray {
|
|
||||||
tags2Dash[tag] = append(tags2Dash[tag], i)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
inter := make([]int, len(allDashboards))
|
|
||||||
for i := range inter {
|
|
||||||
inter[i] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range tagsFromReq {
|
|
||||||
inter = Intersection(inter, tags2Dash[tag])
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredDashboards := []*types.Dashboard{}
|
|
||||||
for _, val := range inter {
|
|
||||||
dash := (allDashboards)[val]
|
|
||||||
filteredDashboards = append(filteredDashboards, dash)
|
|
||||||
}
|
|
||||||
|
|
||||||
aH.Respond(w, filteredDashboards)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareQuery(r *http.Request) (string, error) {
|
func prepareQuery(r *http.Request) (string, error) {
|
||||||
var postData *model.DashboardVars
|
var postData *model.DashboardVars
|
||||||
|
|
||||||
@ -1224,6 +1155,114 @@ func prepareQuery(r *http.Request) (string, error) {
|
|||||||
return newQuery, nil
|
return newQuery, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) Get(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, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, "id is missing in the path"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboard := new(dashboardtypes.Dashboard)
|
||||||
|
if aH.CloudIntegrationsController.IsCloudIntegrationDashboardUuid(id) {
|
||||||
|
cloudintegrationDashboard, apiErr := aH.CloudIntegrationsController.GetDashboardById(ctx, orgID, id)
|
||||||
|
if apiErr != nil {
|
||||||
|
render.Error(rw, errorsV2.Wrapf(apiErr, errorsV2.TypeInternal, errorsV2.CodeInternal, "failed to get dashboard"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dashboard = cloudintegrationDashboard
|
||||||
|
} else if aH.IntegrationsController.IsInstalledIntegrationDashboardID(id) {
|
||||||
|
integrationDashboard, apiErr := aH.IntegrationsController.GetInstalledIntegrationDashboardById(ctx, orgID, id)
|
||||||
|
if apiErr != nil {
|
||||||
|
render.Error(rw, errorsV2.Wrapf(apiErr, errorsV2.TypeInternal, errorsV2.CodeInternal, "failed to get dashboard"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dashboard = integrationDashboard
|
||||||
|
} else {
|
||||||
|
dashboardID, err := valuer.NewUUID(id)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sqlDashboard, err := aH.Signoz.Modules.Dashboard.Get(ctx, orgID, dashboardID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dashboard = sqlDashboard
|
||||||
|
}
|
||||||
|
|
||||||
|
gettableDashboard, err := dashboardtypes.NewGettableDashboardFromDashboard(dashboard)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(rw, http.StatusOK, gettableDashboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) List(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
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboards := make([]*dashboardtypes.Dashboard, 0)
|
||||||
|
sqlDashboards, err := aH.Signoz.Modules.Dashboard.List(ctx, orgID)
|
||||||
|
if err != nil && !errorsV2.Ast(err, errorsV2.TypeNotFound) {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if sqlDashboards != nil {
|
||||||
|
dashboards = append(dashboards, sqlDashboards...)
|
||||||
|
}
|
||||||
|
|
||||||
|
installedIntegrationDashboards, apiErr := aH.IntegrationsController.GetDashboardsForInstalledIntegrations(ctx, orgID)
|
||||||
|
if apiErr != nil {
|
||||||
|
zap.L().Error("failed to get dashboards for installed integrations", zap.Error(apiErr))
|
||||||
|
} else {
|
||||||
|
dashboards = append(dashboards, installedIntegrationDashboards...)
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudIntegrationDashboards, apiErr := aH.CloudIntegrationsController.AvailableDashboards(ctx, orgID)
|
||||||
|
if apiErr != nil {
|
||||||
|
zap.L().Error("failed to get dashboards for cloud integrations", zap.Error(apiErr))
|
||||||
|
} else {
|
||||||
|
dashboards = append(dashboards, cloudIntegrationDashboards...)
|
||||||
|
}
|
||||||
|
|
||||||
|
gettableDashboards, err := dashboardtypes.NewGettableDashboardsFromDashboards(dashboards)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
render.Success(rw, http.StatusOK, gettableDashboards)
|
||||||
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) queryDashboardVarsV2(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) queryDashboardVarsV2(w http.ResponseWriter, r *http.Request) {
|
||||||
query, err := prepareQuery(r)
|
query, err := prepareQuery(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1239,121 +1278,6 @@ func (aH *APIHandler) queryDashboardVarsV2(w http.ResponseWriter, r *http.Reques
|
|||||||
aH.Respond(w, dashboardVars)
|
aH.Respond(w, dashboardVars)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) {
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid := mux.Vars(r)["uuid"]
|
|
||||||
|
|
||||||
var postData map[string]interface{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&postData)
|
|
||||||
if err != nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = aH.IsDashboardPostDataSane(&postData)
|
|
||||||
if err != nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboard, apiError := aH.Signoz.Modules.Dashboard.Update(r.Context(), claims.OrgID, claims.Email, uuid, postData)
|
|
||||||
if apiError != nil {
|
|
||||||
render.Error(w, apiError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
aH.Respond(w, dashboard)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) IsDashboardPostDataSane(data *map[string]interface{}) error {
|
|
||||||
val, ok := (*data)["title"]
|
|
||||||
if !ok || val == nil {
|
|
||||||
return fmt.Errorf("title not found in post data")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
uuid := mux.Vars(r)["uuid"]
|
|
||||||
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dashboard, errv2 := aH.Signoz.Modules.Dashboard.Get(r.Context(), claims.OrgID, uuid)
|
|
||||||
|
|
||||||
var apiError *model.ApiError
|
|
||||||
if errv2 != nil {
|
|
||||||
if !errorsV2.Ast(errv2, errorsV2.TypeNotFound) {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if aH.CloudIntegrationsController.IsCloudIntegrationDashboardUuid(uuid) {
|
|
||||||
dashboard, apiError = aH.CloudIntegrationsController.GetDashboardById(
|
|
||||||
r.Context(), claims.OrgID, uuid,
|
|
||||||
)
|
|
||||||
if apiError != nil {
|
|
||||||
RespondError(w, apiError, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
dashboard, apiError = aH.IntegrationsController.GetInstalledIntegrationDashboardById(
|
|
||||||
r.Context(), claims.OrgID, uuid,
|
|
||||||
)
|
|
||||||
if apiError != nil {
|
|
||||||
RespondError(w, apiError, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
aH.Respond(w, dashboard)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var postData map[string]interface{}
|
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&postData)
|
|
||||||
if err != nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = aH.IsDashboardPostDataSane(&postData)
|
|
||||||
if err != nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dash, errv2 := aH.Signoz.Modules.Dashboard.Create(r.Context(), claims.OrgID, claims.Email, postData)
|
|
||||||
if errv2 != nil {
|
|
||||||
render.Error(w, errv2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
aH.Respond(w, dash)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) testRule(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) testRule(w http.ResponseWriter, r *http.Request) {
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -5229,3 +5153,30 @@ func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
aH.Respond(w, resp)
|
aH.Respond(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterTraceFunnelsRoutes adds trace funnels routes
|
||||||
|
func (aH *APIHandler) RegisterTraceFunnelsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||||
|
// Main trace funnels router
|
||||||
|
traceFunnelsRouter := router.PathPrefix("/api/v1/trace-funnels").Subrouter()
|
||||||
|
|
||||||
|
// API endpoints
|
||||||
|
traceFunnelsRouter.HandleFunc("/new",
|
||||||
|
am.EditAccess(aH.Signoz.Handlers.TraceFunnel.New)).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
traceFunnelsRouter.HandleFunc("/list",
|
||||||
|
am.ViewAccess(aH.Signoz.Handlers.TraceFunnel.List)).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
traceFunnelsRouter.HandleFunc("/steps/update",
|
||||||
|
am.EditAccess(aH.Signoz.Handlers.TraceFunnel.UpdateSteps)).
|
||||||
|
Methods(http.MethodPut)
|
||||||
|
|
||||||
|
traceFunnelsRouter.HandleFunc("/{funnel_id}",
|
||||||
|
am.ViewAccess(aH.Signoz.Handlers.TraceFunnel.Get)).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
traceFunnelsRouter.HandleFunc("/{funnel_id}",
|
||||||
|
am.EditAccess(aH.Signoz.Handlers.TraceFunnel.Delete)).
|
||||||
|
Methods(http.MethodDelete)
|
||||||
|
traceFunnelsRouter.HandleFunc("/{funnel_id}",
|
||||||
|
am.EditAccess(aH.Signoz.Handlers.TraceFunnel.UpdateFunnel)).
|
||||||
|
Methods(http.MethodPut)
|
||||||
|
}
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||||
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -176,6 +178,19 @@ func HydrateFileUris(spec interface{}, fs embed.FS, basedir string) (interface{}
|
|||||||
if specMap, ok := spec.(map[string]interface{}); ok {
|
if specMap, ok := spec.(map[string]interface{}); ok {
|
||||||
result := map[string]interface{}{}
|
result := map[string]interface{}{}
|
||||||
for k, v := range specMap {
|
for k, v := range specMap {
|
||||||
|
// Check if this is a dashboards slice and if dot metrics are enabled
|
||||||
|
if k == "dashboards" && constants.IsDotMetricsEnabled {
|
||||||
|
if dashboards, ok := v.([]interface{}); ok {
|
||||||
|
for i, dashboard := range dashboards {
|
||||||
|
if dashboardUri, ok := dashboard.(string); ok {
|
||||||
|
if strings.HasPrefix(dashboardUri, "file://") {
|
||||||
|
dashboards[i] = strings.Replace(dashboardUri, ".json", "_dot.json", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = dashboards
|
||||||
|
}
|
||||||
|
}
|
||||||
hydrated, err := HydrateFileUris(v, fs, basedir)
|
hydrated, err := HydrateFileUris(v, fs, basedir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -200,7 +215,6 @@ func HydrateFileUris(spec interface{}, fs embed.FS, basedir string) (interface{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
return spec, nil
|
return spec, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFileIfUri(fs embed.FS, maybeFileUri string, basedir string) (interface{}, error) {
|
func readFileIfUri(fs embed.FS, maybeFileUri string, basedir string) (interface{}, error) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7741,4 +7741,4 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uuid": "e74aeb83-ac4b-4313-8a97-216b62c8fc59"
|
"uuid": "e74aeb83-ac4b-4313-8a97-216b62c8fc59"
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,797 @@
|
|||||||
|
{
|
||||||
|
"id": "mongo-overview",
|
||||||
|
"description": "This dashboard provides a high-level overview of your MongoDB. It includes read/write performance, most-used replicas, collection metrics etc...",
|
||||||
|
"layout": [
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "0c3d2b15-89be-4d62-a821-b26d93332ed3",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "14504a3c-4a05-4d22-bab3-e22e94f51380",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "dcfb3829-c3f2-44bb-907d-8dc8a6dc4aab",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "bfc9e80b-02bf-4122-b3da-3dd943d35012",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "4c07a7d2-893a-46c2-bcdb-a19b6efeac3a",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "a5a64eec-1034-4aa6-8cb1-05673c4426c6",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "503af589-ef4d-4fe3-8934-c8f7eb480d9a",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "",
|
||||||
|
"tags": [
|
||||||
|
"mongo",
|
||||||
|
"database"
|
||||||
|
],
|
||||||
|
"title": "Mongo overview",
|
||||||
|
"variables": {
|
||||||
|
"a2c21714-a814-4d31-9b56-7367c3208801": {
|
||||||
|
"allSelected": true,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "List of hosts sending mongo metrics",
|
||||||
|
"id": "a2c21714-a814-4d31-9b56-7367c3208801",
|
||||||
|
"modificationUUID": "448e675a-4531-45b1-b434-a9ee809470d6",
|
||||||
|
"multiSelect": true,
|
||||||
|
"name": "host.name",
|
||||||
|
"order": 0,
|
||||||
|
"queryValue": "SELECT JSONExtractString(labels, 'host.name') AS `host.name`\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'mongodb_memory_usage'\nGROUP BY `host.name`",
|
||||||
|
"selectedValue": [
|
||||||
|
"Srikanths-MacBook-Pro.local"
|
||||||
|
],
|
||||||
|
"showALLOption": true,
|
||||||
|
"sort": "ASC",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"widgets": [
|
||||||
|
{
|
||||||
|
"description": "Total number of operations",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "4c07a7d2-893a-46c2-bcdb-a19b6efeac3a",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.operation.count--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.operation.count",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum_rate",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "a468a30b",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "operation--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "operation",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{operation}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "7da5d899-8b06-4139-9a89-47baf9551ff8",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Operations count",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The total time spent performing operations.",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "bfc9e80b-02bf-4122-b3da-3dd943d35012",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.operation.time--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.operation.time",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum_rate",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "31be3166",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "operation--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "operation",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{operation}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "2ca35957-894a-46ae-a2a6-95d7e400d8e1",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Total operations time",
|
||||||
|
"yAxisUnit": "ms"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The number of cache operations",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "dcfb3829-c3f2-44bb-907d-8dc8a6dc4aab",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.cache.operations--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.cache.operations",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum_rate",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "01b45814",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "type--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "type",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "{{type}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "bb439198-dcf5-4767-b0d0-ab5785159b8d",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Cache operations",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "14504a3c-4a05-4d22-bab3-e22e94f51380",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.operation.latency.time--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.operation.latency.time",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "2e165319",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "operation--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "operation",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "888e920b",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Latency",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "4a9cafe8-778b-476c-b825-c04e165bf285",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Read latency",
|
||||||
|
"yAxisUnit": "µs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "a5a64eec-1034-4aa6-8cb1-05673c4426c6",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.operation.latency.time--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.operation.latency.time",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "53b37ca7",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9862c46c",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "operation--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "operation",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "write"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Latency",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "446827eb-a4f2-4ff3-966b-fb65288c983b",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Write latency",
|
||||||
|
"yAxisUnit": "µs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "503af589-ef4d-4fe3-8934-c8f7eb480d9a",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.operation.latency.time--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.operation.latency.time",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "c33ad4b6",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c70ecfd0",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "operation--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "operation",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "=",
|
||||||
|
"value": "command"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Latency",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "7b7b977d-0921-4552-8cfe-d82dfde63ef4",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Command latency",
|
||||||
|
"yAxisUnit": "µs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "0c3d2b15-89be-4d62-a821-b26d93332ed3",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.network.io.receive--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.network.io.receive",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "avg",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "5c9d7fe3",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Bytes received :: {{host.name}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "mongodb.network.io.transmit--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "mongodb.network.io.transmit",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "avg",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "B",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "96520885",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Bytes transmitted :: {{host.name}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "B",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "41eea5bc-f9cf-45c2-92fb-ef226d6b540b",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Network IO",
|
||||||
|
"yAxisUnit": "bytes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,924 @@
|
|||||||
|
{
|
||||||
|
"id": "redis-overview",
|
||||||
|
"description": "This dashboard shows the Redis instance overview. It includes latency, hit/miss rate, connections, and memory information.\n",
|
||||||
|
"layout": [
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "d4c164bc-8fc2-4dbc-aadd-8d17479ca649",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "2fbaef0d-3cdb-4ce3-aa3c-9bbbb41786d9",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 3,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "f5ee1511-0d2b-4404-9ce0-e991837decc2",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "b19c7058-b806-4ea2-974a-ca555b168991",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "bf0deeeb-e926-4234-944c-82bacd96af47",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "a77227c7-16f5-4353-952e-b183c715a61c",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "9698cee2-b1f3-4c0b-8c9f-3da4f0e05f17",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "64a5f303-d7db-44ff-9a0e-948e5c653320",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"h": 3,
|
||||||
|
"i": "3e80a918-69af-4c9a-bc57-a94e1d41b05c",
|
||||||
|
"moved": false,
|
||||||
|
"static": false,
|
||||||
|
"w": 6,
|
||||||
|
"x": 6,
|
||||||
|
"y": 12
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "",
|
||||||
|
"tags": [
|
||||||
|
"redis",
|
||||||
|
"database"
|
||||||
|
],
|
||||||
|
"title": "Redis overview",
|
||||||
|
"variables": {
|
||||||
|
"94f19b3c-ad9f-4b47-a9b2-f312c09fa965": {
|
||||||
|
"allSelected": true,
|
||||||
|
"customValue": "",
|
||||||
|
"description": "List of hosts sending Redis metrics",
|
||||||
|
"id": "94f19b3c-ad9f-4b47-a9b2-f312c09fa965",
|
||||||
|
"key": "94f19b3c-ad9f-4b47-a9b2-f312c09fa965",
|
||||||
|
"modificationUUID": "4c5b0c03-9cbc-425b-8d8e-7152e5c39ba8",
|
||||||
|
"multiSelect": true,
|
||||||
|
"name": "host.name",
|
||||||
|
"order": 0,
|
||||||
|
"queryValue": "SELECT JSONExtractString(labels, 'host.name') AS `host.name`\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'redis.cpu.time'\nGROUP BY `host.name`",
|
||||||
|
"selectedValue": [
|
||||||
|
"Srikanths-MacBook-Pro.local"
|
||||||
|
],
|
||||||
|
"showALLOption": true,
|
||||||
|
"sort": "ASC",
|
||||||
|
"textboxValue": "",
|
||||||
|
"type": "QUERY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"widgets": [
|
||||||
|
{
|
||||||
|
"description": "Rate successful lookup of keys in the main dictionary",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "a77227c7-16f5-4353-952e-b183c715a61c",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.keyspace.hits--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.keyspace.hits",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum_rate",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "e99669ea",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Hit/s across all hosts",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "42c9c117-bfaf-49f7-b528-aad099392295",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Hits/s",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Number of clients pending on a blocking call",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "bf0deeeb-e926-4234-944c-82bacd96af47",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.clients.blocked--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.clients.blocked",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "97247f25",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Blocked clients across all hosts",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "b77a9e11-fb98-4a95-88a8-c3ad25c14369",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Clients blocked",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "b19c7058-b806-4ea2-974a-ca555b168991",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "",
|
||||||
|
"id": "redis.db.keys------false",
|
||||||
|
"isColumn": false,
|
||||||
|
"key": "redis.db.keys",
|
||||||
|
"type": ""
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "b77a9e11-fb98-4a95-88a8-c3ad25c14369",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Keyspace Keys",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Number of changes since the last dump",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "f5ee1511-0d2b-4404-9ce0-e991837decc2",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.rdb.changes_since_last_save--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.rdb.changes_since_last_save",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "d4aef346",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Number of unsaved changes",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "32cedddf-606d-4de1-8c1d-4b7049e6430c",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Unsaved changes",
|
||||||
|
"yAxisUnit": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "2fbaef0d-3cdb-4ce3-aa3c-9bbbb41786d9",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.commands--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.commands",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "458dc402",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [],
|
||||||
|
"having": [],
|
||||||
|
"legend": "ops/s",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "c70de4dd-a68a-42df-a249-6610c296709c",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Command/s",
|
||||||
|
"yAxisUnit": "ops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "d4c164bc-8fc2-4dbc-aadd-8d17479ca649",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.memory.used--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.memory.used",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "394a537e",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Used::{{host.name}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.maxmemory--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.maxmemory",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "max",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "B",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "0c0754da",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Max::{{host.name}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "B",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "2f47df76-f09e-4152-8623-971f0fe66bfe",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Memory usage",
|
||||||
|
"yAxisUnit": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "9698cee2-b1f3-4c0b-8c9f-3da4f0e05f17",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.memory.rss--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.memory.rss",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "4dc9ae49",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Rss::{{host.name}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "fddd043c-1385-481c-9f4c-381f261e1dd9",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "RSS Memory",
|
||||||
|
"yAxisUnit": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "64a5f303-d7db-44ff-9a0e-948e5c653320",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.memory.fragmentation_ratio--float64--Gauge--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.memory.fragmentation_ratio",
|
||||||
|
"type": "Gauge"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "avg",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "79dc25f3",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Rss::{{host.name}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "3e802b07-0249-4d79-a5c7-6580ab535ad0",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Fragmentation ratio",
|
||||||
|
"yAxisUnit": "short"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Number of evicted keys due to maxmemory limit",
|
||||||
|
"fillSpans": false,
|
||||||
|
"id": "3e80a918-69af-4c9a-bc57-a94e1d41b05c",
|
||||||
|
"isStacked": false,
|
||||||
|
"nullZeroValues": "zero",
|
||||||
|
"opacity": "1",
|
||||||
|
"panelTypes": "graph",
|
||||||
|
"query": {
|
||||||
|
"builder": {
|
||||||
|
"queryData": [
|
||||||
|
{
|
||||||
|
"aggregateAttribute": {
|
||||||
|
"dataType": "float64",
|
||||||
|
"id": "redis.keys.evicted--float64--Sum--true",
|
||||||
|
"isColumn": true,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "redis.keys.evicted",
|
||||||
|
"type": "Sum"
|
||||||
|
},
|
||||||
|
"aggregateOperator": "sum_rate",
|
||||||
|
"dataSource": "metrics",
|
||||||
|
"disabled": false,
|
||||||
|
"expression": "A",
|
||||||
|
"filters": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "53d189ac",
|
||||||
|
"key": {
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
},
|
||||||
|
"op": "in",
|
||||||
|
"value": [
|
||||||
|
"{{.host.name}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"op": "AND"
|
||||||
|
},
|
||||||
|
"groupBy": [
|
||||||
|
{
|
||||||
|
"dataType": "string",
|
||||||
|
"id": "host.name--string--tag--false",
|
||||||
|
"isColumn": false,
|
||||||
|
"isJSON": false,
|
||||||
|
"key": "host.name",
|
||||||
|
"type": "tag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"having": [],
|
||||||
|
"legend": "Rss::{{host.name}}",
|
||||||
|
"limit": null,
|
||||||
|
"orderBy": [],
|
||||||
|
"queryName": "A",
|
||||||
|
"reduceTo": "sum",
|
||||||
|
"stepInterval": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryFormulas": []
|
||||||
|
},
|
||||||
|
"clickhouse_sql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "15d1d9d7-eb10-464b-aa7b-33ff211996f7",
|
||||||
|
"promql": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"legend": "",
|
||||||
|
"name": "A",
|
||||||
|
"query": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryType": "builder"
|
||||||
|
},
|
||||||
|
"softMax": null,
|
||||||
|
"softMin": null,
|
||||||
|
"thresholds": [],
|
||||||
|
"timePreferance": "GLOBAL_TIME",
|
||||||
|
"title": "Eviction rate",
|
||||||
|
"yAxisUnit": "short"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
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