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:
Vikrant Gupta 2024-07-29 15:47:09 +05:30 committed by GitHub
parent 7b7cca7db7
commit 1281330c52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 194 additions and 9 deletions

View File

@ -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())

View File

@ -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());
});
});

View File

@ -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' }}
/>
<Typography.Text className="dashboard-title">{title}</Typography.Text>
<Typography.Text className="dashboard-title" data-testid="dashboard-title">
{title}
</Typography.Text>
{isDashboardLocked && <LockKeyhole size={14} />}
</div>
<div className="right-section">
@ -334,13 +345,22 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
<div className="menu-content">
<section className="section-1">
{(isAuthor || role === USER_ROLES.ADMIN) && (
<Button
type="text"
icon={<LockKeyhole size={14} />}
onClick={handleLockDashboardToggle}
<Tooltip
title={
selectedDashboard?.created_by === 'integration' &&
'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 && (

View File

@ -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: {},
},
},
};

View File

@ -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)),
),

View File

@ -42,6 +42,7 @@ const mockStored = (role?: string): any =>
accessJwt: '',
refreshJwt: '',
},
isLoggedIn: true,
org: [
{
createdAt: 0,