mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PublicDashboards: Orphaned public dashboard item list modified (#58014)
PublicDashboards: Orphaned public dashboard item list modified
This commit is contained in:
parent
088a4e02e8
commit
fb5401b8b9
@ -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")
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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};
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user