mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-31 19:41:58 +08:00
fix: disable the unlock dashboard btn for integration dashboards (#5573)
* fix: disable the unlock dashboard btn for integration dashboards * chore: added test cases for the integration / non integration dashboards
This commit is contained in:
parent
7b7cca7db7
commit
1281330c52
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
"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
|
// Get the dashboard UUID from the request
|
||||||
uuid := mux.Vars(r)["uuid"]
|
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)
|
dashboard, err := dashboards.GetDashboard(r.Context(), uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())
|
||||||
|
@ -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 <div>MockDateTimeSelection</div>;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<MemoryRouter initialEntries={['/dashboard/4']}>
|
||||||
|
<DashboardProvider>
|
||||||
|
<DashboardDescription
|
||||||
|
handle={{
|
||||||
|
active: false,
|
||||||
|
enter: (): Promise<void> => Promise.resolve(),
|
||||||
|
exit: (): Promise<void> => Promise.resolve(),
|
||||||
|
node: { current: null },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DashboardProvider>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<MemoryRouter initialEntries={['/dashboard/4']}>
|
||||||
|
<DashboardProvider>
|
||||||
|
<DashboardDescription
|
||||||
|
handle={{
|
||||||
|
active: false,
|
||||||
|
enter: (): Promise<void> => Promise.resolve(),
|
||||||
|
exit: (): Promise<void> => Promise.resolve(),
|
||||||
|
node: { current: null },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DashboardProvider>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
});
|
@ -1,7 +1,16 @@
|
|||||||
import './Description.styles.scss';
|
import './Description.styles.scss';
|
||||||
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
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 logEvent from 'api/common/logEvent';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
||||||
import { dashboardHelpMessage } from 'components/facingIssueBtn/util';
|
import { dashboardHelpMessage } from 'components/facingIssueBtn/util';
|
||||||
@ -308,7 +317,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
alt="dashboard-img"
|
alt="dashboard-img"
|
||||||
style={{ width: '16px', height: '16px' }}
|
style={{ width: '16px', height: '16px' }}
|
||||||
/>
|
/>
|
||||||
<Typography.Text className="dashboard-title">{title}</Typography.Text>
|
<Typography.Text className="dashboard-title" data-testid="dashboard-title">
|
||||||
|
{title}
|
||||||
|
</Typography.Text>
|
||||||
{isDashboardLocked && <LockKeyhole size={14} />}
|
{isDashboardLocked && <LockKeyhole size={14} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="right-section">
|
<div className="right-section">
|
||||||
@ -334,13 +345,22 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
<div className="menu-content">
|
<div className="menu-content">
|
||||||
<section className="section-1">
|
<section className="section-1">
|
||||||
{(isAuthor || role === USER_ROLES.ADMIN) && (
|
{(isAuthor || role === USER_ROLES.ADMIN) && (
|
||||||
<Button
|
<Tooltip
|
||||||
type="text"
|
title={
|
||||||
icon={<LockKeyhole size={14} />}
|
selectedDashboard?.created_by === 'integration' &&
|
||||||
onClick={handleLockDashboardToggle}
|
'Dashboards created by integrations cannot be unlocked'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{isDashboardLocked ? 'Unlock Dashboard' : 'Lock Dashboard'}
|
<Button
|
||||||
</Button>
|
type="text"
|
||||||
|
icon={<LockKeyhole size={14} />}
|
||||||
|
disabled={selectedDashboard?.created_by === 'integration'}
|
||||||
|
onClick={handleLockDashboardToggle}
|
||||||
|
data-testid="lock-unlock-dashboard"
|
||||||
|
>
|
||||||
|
{isDashboardLocked ? 'Unlock Dashboard' : 'Lock Dashboard'}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isDashboardLocked && editDashboard && (
|
{!isDashboardLocked && editDashboard && (
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
export const dashboardSuccessResponse = {
|
export const dashboardSuccessResponse = {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: [
|
data: [
|
||||||
@ -48,3 +49,53 @@ export const dashboardEmptyState = {
|
|||||||
status: 'sucsess',
|
status: 'sucsess',
|
||||||
data: [],
|
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: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { rest } from 'msw';
|
import { rest } from 'msw';
|
||||||
|
|
||||||
import { billingSuccessResponse } from './__mockdata__/billing';
|
import { billingSuccessResponse } from './__mockdata__/billing';
|
||||||
import { dashboardSuccessResponse } from './__mockdata__/dashboards';
|
import {
|
||||||
|
dashboardSuccessResponse,
|
||||||
|
getDashboardById,
|
||||||
|
} from './__mockdata__/dashboards';
|
||||||
import { explorerView } from './__mockdata__/explorer_views';
|
import { explorerView } from './__mockdata__/explorer_views';
|
||||||
import { inviteUser } from './__mockdata__/invite_user';
|
import { inviteUser } from './__mockdata__/invite_user';
|
||||||
import { licensesSuccessResponse } from './__mockdata__/licenses';
|
import { licensesSuccessResponse } from './__mockdata__/licenses';
|
||||||
@ -141,6 +144,10 @@ export const handlers = [
|
|||||||
res(ctx.status(200), ctx.json(dashboardSuccessResponse)),
|
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) =>
|
rest.get('http://localhost/api/v1/invite', (_, res, ctx) =>
|
||||||
res(ctx.status(200), ctx.json(inviteUser)),
|
res(ctx.status(200), ctx.json(inviteUser)),
|
||||||
),
|
),
|
||||||
|
@ -42,6 +42,7 @@ const mockStored = (role?: string): any =>
|
|||||||
accessJwt: '',
|
accessJwt: '',
|
||||||
refreshJwt: '',
|
refreshJwt: '',
|
||||||
},
|
},
|
||||||
|
isLoggedIn: true,
|
||||||
org: [
|
org: [
|
||||||
{
|
{
|
||||||
createdAt: 0,
|
createdAt: 0,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user