PublicDashboards: Orphaned public dashboard item list modified (#58014)

PublicDashboards: Orphaned public dashboard item list modified
This commit is contained in:
juanicabanas 2022-11-01 16:56:19 -03:00 committed by GitHub
parent 088a4e02e8
commit fb5401b8b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 149 additions and 55 deletions

View File

@ -38,9 +38,9 @@ func (d *PublicDashboardStoreImpl) FindAll(ctx context.Context, orgId int64) ([]
resp := make([]PublicDashboardListResponse, 0)
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
sess.Table("dashboard_public").
sess.Table("dashboard_public").Select(
"dashboard_public.uid, dashboard_public.access_token, dashboard.uid as dashboard_uid, dashboard_public.is_enabled, dashboard.title").
Join("LEFT", "dashboard", "dashboard.uid = dashboard_public.dashboard_uid AND dashboard.org_id = dashboard_public.org_id").
Cols("dashboard_public.uid", "dashboard_public.access_token", "dashboard_public.dashboard_uid", "dashboard_public.is_enabled", "dashboard.title").
Where("dashboard_public.org_id = ?", orgId).
OrderBy(" is_enabled DESC, dashboard.title IS NULL, dashboard.title ASC")

View File

@ -32,6 +32,23 @@ const publicDashboardListResponse: ListPublicDashboardResponse[] = [
},
];
const orphanedDashboardListResponse: ListPublicDashboardResponse[] = [
{
uid: 'SdZwuCZVz2',
accessToken: 'beeaf92f6ab3467f80b2be922c7741ab',
title: '',
dashboardUid: '',
isEnabled: false,
},
{
uid: 'EuiEbd3nz2',
accessToken: '8687b0498ccf4babb2f92810d8563b33',
title: '',
dashboardUid: '',
isEnabled: true,
},
];
const server = setupServer(
rest.get('/api/dashboards/public-dashboards', (_, res, ctx) =>
res(ctx.status(200), ctx.json(publicDashboardListResponse))
@ -107,7 +124,11 @@ describe('Show table', () => {
});
it('renders public dashboards in a good way without trashcan', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
await renderPublicDashboardItems();
await renderPublicDashboardTable(true);
publicDashboardListResponse.forEach((pd, idx) => {
renderPublicDashboardItemCorrectly(pd, idx, false);
});
const tableBody = screen.getAllByRole('rowgroup')[1];
const tableRows = within(tableBody).getAllByRole('row');
@ -120,7 +141,11 @@ describe('Show table', () => {
});
it('renders public dashboards in a good way with trashcan', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
await renderPublicDashboardItems();
await renderPublicDashboardTable(true);
publicDashboardListResponse.forEach((pd, idx) => {
renderPublicDashboardItemCorrectly(pd, idx, true);
});
const tableBody = screen.getAllByRole('rowgroup')[1];
const tableRows = within(tableBody).getAllByRole('row');
@ -131,17 +156,25 @@ describe('Show table', () => {
expect(within(rowDataCells[2]).getByTestId(selectors.ListItem.trashcanButton));
});
});
it('renders public dashboards items correctly', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
await renderPublicDashboardTable(true);
publicDashboardListResponse.forEach((pd, idx) => {
renderPublicDashboardItemCorrectly(pd, idx, true);
});
});
});
describe('Delete public dashboard', () => {
it('when user does not have public dashboard write permissions, then dashboards are listed without delete button ', async () => {
it('when user does not have public dashboard write permissions, then dashboards are listed without delete button', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
await renderPublicDashboardTable(true);
const tableBody = screen.getAllByRole('rowgroup')[1];
expect(within(tableBody).queryAllByTestId(selectors.ListItem.trashcanButton)).toHaveLength(0);
});
it('when user has public dashboard write permissions, then dashboards are listed with delete button ', async () => {
it('when user has public dashboard write permissions, then dashboards are listed with delete button', async () => {
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
await renderPublicDashboardTable(true);
@ -152,20 +185,46 @@ describe('Delete public dashboard', () => {
});
});
const renderPublicDashboardItems = async () => {
await renderPublicDashboardTable(true);
describe('Orphaned public dashboard', () => {
it('renders orphaned and non orphaned public dashboards items correctly', async () => {
const response = [...publicDashboardListResponse, ...orphanedDashboardListResponse];
server.use(
rest.get('/api/dashboards/public-dashboards', (req, res, ctx) => {
return res(ctx.status(200), ctx.json(response));
})
);
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(true);
await renderPublicDashboardTable(true);
response.forEach((pd, idx) => {
renderPublicDashboardItemCorrectly(pd, idx, true);
});
});
});
const renderPublicDashboardItemCorrectly = (pd: ListPublicDashboardResponse, idx: number, hasWriteAccess: boolean) => {
const isOrphaned = !pd.dashboardUid;
const tableBody = screen.getAllByRole('rowgroup')[1];
const tableRows = within(tableBody).getAllByRole('row');
publicDashboardListResponse.forEach((pd, idx) => {
const tableRow = tableRows[idx];
const rowDataCells = within(tableRow).getAllByRole('cell');
expect(rowDataCells).toHaveLength(3);
const tableRow = tableRows[idx];
const rowDataCells = within(tableRow).getAllByRole('cell');
expect(rowDataCells).toHaveLength(3);
expect(within(rowDataCells[0]).getByText(pd.title));
expect(within(rowDataCells[1]).getByText(pd.isEnabled ? 'enabled' : 'disabled'));
expect(within(rowDataCells[2]).getByTestId(selectors.ListItem.linkButton));
expect(within(rowDataCells[2]).getByTestId(selectors.ListItem.configButton));
});
const statusTag = within(rowDataCells[1]).getByText(pd.isEnabled ? 'enabled' : 'disabled');
const linkButton = within(rowDataCells[2]).getByTestId(selectors.ListItem.linkButton);
const configButton = within(rowDataCells[2]).getByTestId(selectors.ListItem.configButton);
const trashcanButton = within(rowDataCells[2]).queryByTestId(selectors.ListItem.trashcanButton);
expect(within(rowDataCells[0]).getByText(isOrphaned ? 'Orphaned public dashboard' : pd.title)).toBeInTheDocument();
expect(statusTag).toBeInTheDocument();
isOrphaned ? expect(statusTag).toHaveStyle('background-color: rgb(110, 110, 110)') : expect(statusTag).toBeEnabled();
isOrphaned || !pd.isEnabled
? expect(linkButton).toHaveStyle('pointer-events: none')
: expect(linkButton).not.toHaveStyle('pointer-events: none');
isOrphaned
? expect(configButton).toHaveStyle('pointer-events: none')
: expect(configButton).not.toHaveStyle('pointer-events: none');
hasWriteAccess ? expect(trashcanButton).toBeEnabled() : expect(trashcanButton).toBeNull();
};

View File

@ -12,6 +12,8 @@ import { useListPublicDashboardsQuery } from 'app/features/dashboard/api/publicD
import { isOrgAdmin } from 'app/features/plugins/admin/permissions';
import { AccessControlAction } from 'app/types';
import { ListPublicDashboardResponse } from '../../types';
import { DeletePublicDashboardButton } from './DeletePublicDashboardButton';
export const viewPublicDashboardUrl = (accessToken: string): string =>
@ -41,45 +43,64 @@ export const PublicDashboardListTable = () => {
</tr>
</thead>
<tbody>
{publicDashboards?.map((pd) => (
<tr key={pd.uid}>
<td className={styles.titleTd}>
<Tooltip content={pd.title} placement="top">
<Link className={styles.link} href={`/d/${pd.dashboardUid}`}>
{pd.title}
</Link>
</Tooltip>
</td>
<td>
<Tag name={pd.isEnabled ? 'enabled' : 'disabled'} colorIndex={pd.isEnabled ? 20 : 15} />
</td>
<td>
<ButtonGroup className={styles.buttonGroup}>
<LinkButton
href={viewPublicDashboardUrl(pd.accessToken)}
fill="text"
size={responsiveSize}
title={pd.isEnabled ? 'View public dashboard' : 'Public dashboard is disabled'}
target="_blank"
disabled={!pd.isEnabled}
data-testid={selectors.ListItem.linkButton}
{publicDashboards?.map((pd: ListPublicDashboardResponse) => {
const isOrphaned = !pd.dashboardUid;
return (
<tr key={pd.uid}>
<td className={styles.titleTd}>
<Tooltip
content={!isOrphaned ? pd.title : 'The linked dashboard has already been deleted'}
placement="top"
>
<Icon size={responsiveSize} name="external-link-alt" />
</LinkButton>
<LinkButton
fill="text"
size={responsiveSize}
href={`/d/${pd.dashboardUid}?shareView=share`}
title="Configure public dashboard"
data-testid={selectors.ListItem.configButton}
>
<Icon size={responsiveSize} name="cog" />
</LinkButton>
{hasWritePermissions && <DeletePublicDashboardButton publicDashboard={pd} size={responsiveSize} />}
</ButtonGroup>
</td>
</tr>
))}
{!isOrphaned ? (
<Link className={styles.link} href={`/d/${pd.dashboardUid}`}>
{pd.title}
</Link>
) : (
<div className={styles.orphanedTitle}>
<p>Orphaned public dashboard</p>
<Icon name="info-circle" className={styles.orphanedInfoIcon} />
</div>
)}
</Tooltip>
</td>
<td>
<Tag
name={pd.isEnabled ? 'enabled' : 'disabled'}
colorIndex={isOrphaned ? 9 : pd.isEnabled ? 20 : 15}
/>
</td>
<td>
<ButtonGroup className={styles.buttonGroup}>
<LinkButton
href={viewPublicDashboardUrl(pd.accessToken)}
fill="text"
size={responsiveSize}
title={pd.isEnabled ? 'View public dashboard' : 'Public dashboard is disabled'}
target="_blank"
disabled={!pd.isEnabled || isOrphaned}
data-testid={selectors.ListItem.linkButton}
>
<Icon size={responsiveSize} name="external-link-alt" />
</LinkButton>
<LinkButton
fill="text"
size={responsiveSize}
href={`/d/${pd.dashboardUid}?shareView=share`}
title="Configure public dashboard"
disabled={isOrphaned}
data-testid={selectors.ListItem.configButton}
>
<Icon size={responsiveSize} name="cog" />
</LinkButton>
{hasWritePermissions && (
<DeletePublicDashboardButton publicDashboard={pd} size={responsiveSize} />
)}
</ButtonGroup>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
@ -110,5 +131,19 @@ function getStyles(theme: GrafanaTheme2, isMobile: boolean) {
buttonGroup: css`
justify-content: ${isMobile ? 'space-between' : 'end'};
`,
orphanedTitle: css`
display: flex;
align-items: center;
p {
margin: ${theme.spacing(0, 1, 0, 0)};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`,
orphanedInfoIcon: css`
color: ${theme.colors.text.link};
`,
};
}