Skip to content

Commit b0bc701

Browse files
authored
feat(dashboards): Add foundation for manage page table view (#95157)
Adds the foundation for the split tables in the manage page starting with owned dashboards. I'll need to create a similar hook for shared dashboards and create a similar table below. Most of the functionality is not implemented and thus you cannot favorite, duplicate, or delete dashboards from this view. It is also missing information such as environments, filters, and last viewed because they are not yet exposed. I have tickets for each of these as subtasks.
1 parent 50905af commit b0bc701

File tree

6 files changed

+330
-9
lines changed

6 files changed

+330
-9
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {useApiQuery} from 'sentry/utils/queryClient';
2+
import useOrganization from 'sentry/utils/useOrganization';
3+
import type {DashboardListItem} from 'sentry/views/dashboards/types';
4+
5+
export function useOwnedDashboards({
6+
query,
7+
cursor,
8+
sort,
9+
enabled,
10+
}: {
11+
cursor: string;
12+
enabled: boolean;
13+
query: string;
14+
sort: string;
15+
}) {
16+
const organization = useOrganization();
17+
return useApiQuery<DashboardListItem[]>(
18+
[
19+
`/organizations/${organization.slug}/dashboards/`,
20+
{
21+
query: {
22+
query,
23+
cursor,
24+
sort,
25+
filter: 'owned',
26+
pin: 'favorites',
27+
per_page: 20,
28+
},
29+
},
30+
],
31+
{
32+
staleTime: 0,
33+
enabled,
34+
}
35+
);
36+
}

static/app/views/dashboards/manage/index.spec.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,10 @@ describe('Dashboards > Detail', function () {
229229
},
230230
});
231231

232-
expect(await screen.findByTestId('list')).toBeInTheDocument();
233-
await userEvent.click(await screen.findByTestId('list'));
232+
expect(await screen.findByTestId('table')).toBeInTheDocument();
233+
await userEvent.click(await screen.findByTestId('table'));
234234

235-
expect(localStorage.setItem).toHaveBeenCalledWith(LAYOUT_KEY, '"list"');
235+
expect(localStorage.setItem).toHaveBeenCalledWith(LAYOUT_KEY, '"table"');
236236
expect(await screen.findByTestId('grid-editable')).toBeInTheDocument();
237237

238238
expect(await screen.findByTestId('grid')).toBeInTheDocument();

static/app/views/dashboards/manage/index.tsx

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ import {useLocation} from 'sentry/utils/useLocation';
3838
import {useNavigate} from 'sentry/utils/useNavigate';
3939
import useOrganization from 'sentry/utils/useOrganization';
4040
import {getDashboardTemplates} from 'sentry/views/dashboards/data';
41+
import {useOwnedDashboards} from 'sentry/views/dashboards/hooks/useOwnedDashboards';
4142
import {
4243
assignDefaultLayout,
4344
getInitialColumnDepths,
4445
} from 'sentry/views/dashboards/layoutUtils';
4546
import DashboardTable from 'sentry/views/dashboards/manage/dashboardTable';
47+
import OwnedDashboardsTable, {
48+
OWNED_CURSOR_KEY,
49+
} from 'sentry/views/dashboards/manage/tableView/ownedDashboardsTable';
4650
import type {DashboardsLayout} from 'sentry/views/dashboards/manage/types';
4751
import type {DashboardDetails, DashboardListItem} from 'sentry/views/dashboards/types';
4852
import {usePrefersStackedNav} from 'sentry/views/nav/usePrefersStackedNav';
@@ -82,6 +86,13 @@ function shouldShowTemplates(): boolean {
8286

8387
function getDashboardsOverviewLayout(): DashboardsLayout {
8488
const dashboardsLayout = localStorage.getItem(LAYOUT_KEY);
89+
90+
// There was a bug where the layout was saved as 'list' instead of 'table'
91+
// this coerces it back to TABLE in case we still rely on it anywhere
92+
if (dashboardsLayout === 'list') {
93+
return TABLE;
94+
}
95+
8596
return dashboardsLayout === GRID || dashboardsLayout === TABLE
8697
? dashboardsLayout
8798
: GRID;
@@ -128,9 +139,24 @@ function ManageDashboards() {
128139
},
129140
},
130141
],
131-
{staleTime: 0}
142+
{
143+
staleTime: 0,
144+
enabled: !(
145+
organization.features.includes('dashboards-starred-reordering') &&
146+
dashboardsLayout === TABLE
147+
),
148+
}
132149
);
133150

151+
const ownedDashboards = useOwnedDashboards({
152+
query: decodeScalar(location.query.query, ''),
153+
cursor: decodeScalar(location.query[OWNED_CURSOR_KEY], ''),
154+
sort: getActiveSort()!.value,
155+
enabled:
156+
organization.features.includes('dashboards-starred-reordering') &&
157+
dashboardsLayout === TABLE,
158+
});
159+
134160
const dashboardsPageLinks = getResponseHeader?.('Link') ?? '';
135161

136162
function setRowsAndColumns(containerWidth: number) {
@@ -275,14 +301,14 @@ function ManageDashboards() {
275301
aria-label={t('Layout Control')}
276302
>
277303
<SegmentedControl.Item
278-
key="grid"
279-
textValue="grid"
304+
key={GRID}
305+
textValue={GRID}
280306
aria-label={t('Grid View')}
281307
icon={<IconGrid />}
282308
/>
283309
<SegmentedControl.Item
284-
key="list"
285-
textValue="list"
310+
key={TABLE}
311+
textValue={TABLE}
286312
aria-label={t('List View')}
287313
icon={<IconList />}
288314
/>
@@ -320,6 +346,12 @@ function ManageDashboards() {
320346
rowCount={rowCount}
321347
columnCount={columnCount}
322348
/>
349+
) : organization.features.includes('dashboards-starred-reordering') ? (
350+
<OwnedDashboardsTable
351+
dashboards={ownedDashboards.data ?? []}
352+
isLoading={ownedDashboards.isLoading}
353+
pageLinks={ownedDashboards.getResponseHeader?.('Link') ?? undefined}
354+
/>
323355
) : (
324356
<DashboardTable
325357
api={api}
@@ -492,7 +524,10 @@ function ManageDashboards() {
492524
<div ref={dashboardGridRef} id="dashboard-list-container">
493525
{renderDashboards()}
494526
</div>
495-
{renderPagination()}
527+
{!(
528+
organization.features.includes('dashboards-starred-reordering') &&
529+
dashboardsLayout === TABLE
530+
) && renderPagination()}
496531
</Layout.Main>
497532
</Layout.Body>
498533
</NoProjectMessage>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {t} from 'sentry/locale';
2+
3+
import type {DashboardTableProps} from './table';
4+
import {DashboardTable} from './table';
5+
6+
export const OWNED_CURSOR_KEY = 'ownedCursor';
7+
8+
function OwnedDashboardsTable({
9+
dashboards,
10+
isLoading,
11+
pageLinks,
12+
}: Pick<DashboardTableProps, 'dashboards' | 'isLoading' | 'pageLinks'>) {
13+
return (
14+
<DashboardTable
15+
title={t('Created by Me')}
16+
cursorKey={OWNED_CURSOR_KEY}
17+
dashboards={dashboards}
18+
isLoading={isLoading}
19+
pageLinks={pageLinks}
20+
/>
21+
);
22+
}
23+
24+
export default OwnedDashboardsTable;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {DashboardListItemFixture} from 'sentry-fixture/dashboard';
2+
import {UserFixture} from 'sentry-fixture/user';
3+
4+
import {render, screen} from 'sentry-test/reactTestingLibrary';
5+
6+
import {DashboardTable} from './table';
7+
8+
describe('DashboardTable', () => {
9+
it('should render', () => {
10+
render(
11+
<DashboardTable
12+
dashboards={[
13+
DashboardListItemFixture({
14+
id: '1',
15+
title: 'Test',
16+
projects: [],
17+
createdBy: UserFixture({
18+
name: 'Test User',
19+
}),
20+
isFavorited: true,
21+
widgetPreview: [],
22+
}),
23+
]}
24+
isLoading={false}
25+
title="My custom dashboards"
26+
cursorKey="test"
27+
/>
28+
);
29+
30+
expect(screen.getByText('My custom dashboards')).toBeInTheDocument();
31+
expect(screen.getByText('Test')).toBeInTheDocument();
32+
expect(screen.getByText('My Projects')).toBeInTheDocument();
33+
34+
// 0 widgets
35+
expect(screen.getByText('0')).toBeInTheDocument();
36+
37+
// The dashboard is starred, so the button should prompt "Unstar"
38+
expect(screen.getByLabelText('Unstar')).toBeInTheDocument();
39+
});
40+
});

0 commit comments

Comments
 (0)