diff --git a/ee/query-service/app/api/dashboard.go b/ee/query-service/app/api/dashboard.go index 0628ae18f6..51fe6c2ded 100644 --- a/ee/query-service/app/api/dashboard.go +++ b/ee/query-service/app/api/dashboard.go @@ -1,7 +1,9 @@ package api import ( + "errors" "net/http" + "strings" "github.com/gorilla/mux" "go.signoz.io/signoz/pkg/query-service/app/dashboards" @@ -29,6 +31,10 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request // Get the dashboard UUID from the request uuid := mux.Vars(r)["uuid"] + if strings.HasPrefix(uuid,"integration") { + RespondError(w, &model.ApiError{Typ: model.ErrorForbidden, Err: errors.New("dashboards created by integrations cannot be unlocked")}, "You are not authorized to lock/unlock this dashboard") + return + } dashboard, err := dashboards.GetDashboard(r.Context(), uuid) if err != nil { RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error()) diff --git a/frontend/src/container/NewDashboard/DashboardDescription/__tests__/DashboardDescription.test.tsx b/frontend/src/container/NewDashboard/DashboardDescription/__tests__/DashboardDescription.test.tsx new file mode 100644 index 0000000000..8e302042a3 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardDescription/__tests__/DashboardDescription.test.tsx @@ -0,0 +1,100 @@ +import { getNonIntegrationDashboardById } from 'mocks-server/__mockdata__/dashboards'; +import { server } from 'mocks-server/server'; +import { rest } from 'msw'; +import { DashboardProvider } from 'providers/Dashboard/Dashboard'; +import { MemoryRouter, useLocation } from 'react-router-dom'; +import { fireEvent, render, screen, waitFor } from 'tests/test-utils'; + +import DashboardDescription from '..'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), + useRouteMatch: jest.fn().mockReturnValue({ + params: { + dashboardId: 4, + }, + }), +})); + +jest.mock( + 'container/TopNav/DateTimeSelectionV2/index.tsx', + () => + function MockDateTimeSelection(): JSX.Element { + return
MockDateTimeSelection
; + }, +); + +describe('Dashboard landing page actions header tests', () => { + it('unlock dashboard should be disabled for integrations created dashboards', async () => { + const mockLocation = { + pathname: `${process.env.FRONTEND_API_ENDPOINT}/dashboard/4`, + search: '', + }; + (useLocation as jest.Mock).mockReturnValue(mockLocation); + const { getByTestId } = render( + + + => Promise.resolve(), + exit: (): Promise => Promise.resolve(), + node: { current: null }, + }} + /> + + , + ); + + await waitFor(() => + expect(getByTestId('dashboard-title')).toHaveTextContent('thor'), + ); + + const dashboardSettingsTrigger = getByTestId('options'); + + await fireEvent.click(dashboardSettingsTrigger); + + const lockUnlockButton = screen.getByTestId('lock-unlock-dashboard'); + + await waitFor(() => expect(lockUnlockButton).toBeDisabled()); + }); + it('unlock dashboard should not be disabled for non integration created dashboards', async () => { + const mockLocation = { + pathname: `${process.env.FRONTEND_API_ENDPOINT}/dashboard/4`, + search: '', + }; + (useLocation as jest.Mock).mockReturnValue(mockLocation); + server.use( + rest.get('http://localhost/api/v1/dashboards/4', (_, res, ctx) => + res(ctx.status(200), ctx.json(getNonIntegrationDashboardById)), + ), + ); + const { getByTestId } = render( + + + => Promise.resolve(), + exit: (): Promise => Promise.resolve(), + node: { current: null }, + }} + /> + + , + ); + + await waitFor(() => + expect(getByTestId('dashboard-title')).toHaveTextContent('thor'), + ); + + const dashboardSettingsTrigger = getByTestId('options'); + + await fireEvent.click(dashboardSettingsTrigger); + + const lockUnlockButton = screen.getByTestId('lock-unlock-dashboard'); + + await waitFor(() => expect(lockUnlockButton).not.toBeDisabled()); + }); +}); diff --git a/frontend/src/container/NewDashboard/DashboardDescription/index.tsx b/frontend/src/container/NewDashboard/DashboardDescription/index.tsx index d3c959ca8b..1a60e74de9 100644 --- a/frontend/src/container/NewDashboard/DashboardDescription/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardDescription/index.tsx @@ -1,7 +1,16 @@ import './Description.styles.scss'; import { PlusOutlined } from '@ant-design/icons'; -import { Button, Card, Input, Modal, Popover, Tag, Typography } from 'antd'; +import { + Button, + Card, + Input, + Modal, + Popover, + Tag, + Tooltip, + Typography, +} from 'antd'; import logEvent from 'api/common/logEvent'; import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn'; import { dashboardHelpMessage } from 'components/facingIssueBtn/util'; @@ -308,7 +317,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element { alt="dashboard-img" style={{ width: '16px', height: '16px' }} /> - {title} + + {title} + {isDashboardLocked && }
@@ -334,13 +345,22 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
{(isAuthor || role === USER_ROLES.ADMIN) && ( - + + )} {!isDashboardLocked && editDashboard && ( diff --git a/frontend/src/mocks-server/__mockdata__/dashboards.ts b/frontend/src/mocks-server/__mockdata__/dashboards.ts index 4f1a6b78e7..40d9cb48d9 100644 --- a/frontend/src/mocks-server/__mockdata__/dashboards.ts +++ b/frontend/src/mocks-server/__mockdata__/dashboards.ts @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/no-duplicate-string */ export const dashboardSuccessResponse = { status: 'success', data: [ @@ -48,3 +49,53 @@ export const dashboardEmptyState = { status: 'sucsess', data: [], }; + +export const getDashboardById = { + status: 'success', + data: { + id: 1, + uuid: '1', + created_at: '2022-11-16T13:29:47.064874419Z', + created_by: 'integration', + updated_at: '2024-05-21T06:41:30.546630961Z', + updated_by: 'thor@avengers.io', + isLocked: true, + data: { + collapsableRowsMigrated: true, + description: '', + name: '', + panelMap: {}, + tags: ['linux'], + title: 'thor', + uploadedGrafana: false, + uuid: '', + version: '', + variables: {}, + }, + }, +}; + +export const getNonIntegrationDashboardById = { + status: 'success', + data: { + id: 1, + uuid: '1', + created_at: '2022-11-16T13:29:47.064874419Z', + created_by: 'thor', + updated_at: '2024-05-21T06:41:30.546630961Z', + updated_by: 'thor@avengers.io', + isLocked: true, + data: { + collapsableRowsMigrated: true, + description: '', + name: '', + panelMap: {}, + tags: ['linux'], + title: 'thor', + uploadedGrafana: false, + uuid: '', + version: '', + variables: {}, + }, + }, +}; diff --git a/frontend/src/mocks-server/handlers.ts b/frontend/src/mocks-server/handlers.ts index f96309380a..d46aa52420 100644 --- a/frontend/src/mocks-server/handlers.ts +++ b/frontend/src/mocks-server/handlers.ts @@ -1,7 +1,10 @@ import { rest } from 'msw'; import { billingSuccessResponse } from './__mockdata__/billing'; -import { dashboardSuccessResponse } from './__mockdata__/dashboards'; +import { + dashboardSuccessResponse, + getDashboardById, +} from './__mockdata__/dashboards'; import { explorerView } from './__mockdata__/explorer_views'; import { inviteUser } from './__mockdata__/invite_user'; import { licensesSuccessResponse } from './__mockdata__/licenses'; @@ -141,6 +144,10 @@ export const handlers = [ res(ctx.status(200), ctx.json(dashboardSuccessResponse)), ), + rest.get('http://localhost/api/v1/dashboards/4', (_, res, ctx) => + res(ctx.status(200), ctx.json(getDashboardById)), + ), + rest.get('http://localhost/api/v1/invite', (_, res, ctx) => res(ctx.status(200), ctx.json(inviteUser)), ), diff --git a/frontend/src/tests/test-utils.tsx b/frontend/src/tests/test-utils.tsx index ff2d3c7e51..4eced41eff 100644 --- a/frontend/src/tests/test-utils.tsx +++ b/frontend/src/tests/test-utils.tsx @@ -42,6 +42,7 @@ const mockStored = (role?: string): any => accessJwt: '', refreshJwt: '', }, + isLoggedIn: true, org: [ { createdAt: 0,