Skip to content

Commit e08e9d0

Browse files
authored
feat(dashboards): Dashboards landing page layout toggle (#80790)
Adds a toggle to change the dashboards layout from grid to table. This is under feature flag and available to only me right now (since there is no functionality to the toggle as of yet). Actual table layout will be implemented in further PRs. <img width="1277" alt="image" src="https://github.com/user-attachments/assets/019b15fd-3c90-40f1-ab43-deed724a4793"> ALSO I am aware that the icon for grid right now is the dashboards icon, Vasudha is working on getting the proper icon for the grid.
1 parent 3c5425e commit e08e9d0

File tree

2 files changed

+69
-5
lines changed

2 files changed

+69
-5
lines changed

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
77
import selectEvent from 'sentry-test/selectEvent';
88

99
import ProjectsStore from 'sentry/stores/projectsStore';
10+
import localStorage from 'sentry/utils/localStorage';
1011
import {useNavigate} from 'sentry/utils/useNavigate';
11-
import ManageDashboards from 'sentry/views/dashboards/manage';
12+
import ManageDashboards, {LAYOUT_KEY} from 'sentry/views/dashboards/manage';
1213
import {getPaginationPageLink} from 'sentry/views/organizationStats/utils';
1314

15+
jest.mock('sentry/utils/localStorage');
16+
1417
const FEATURES = [
1518
'global-views',
1619
'dashboards-basic',
@@ -211,4 +214,24 @@ describe('Dashboards > Detail', function () {
211214

212215
expect(mockNavigate).not.toHaveBeenCalled();
213216
});
217+
218+
it('toggles between grid and list view', async function () {
219+
render(<ManageDashboards />, {
220+
...RouteComponentPropsFixture(),
221+
organization: {
222+
...mockAuthorizedOrg,
223+
features: [...FEATURES, 'dashboards-table-view'],
224+
},
225+
});
226+
227+
expect(await screen.findByTestId('list')).toBeInTheDocument();
228+
await userEvent.click(await screen.findByTestId('list'));
229+
230+
expect(localStorage.setItem).toHaveBeenCalledWith(LAYOUT_KEY, '"list"');
231+
232+
expect(await screen.findByTestId('grid')).toBeInTheDocument();
233+
await userEvent.click(await screen.findByTestId('grid'));
234+
235+
expect(localStorage.setItem).toHaveBeenCalledWith(LAYOUT_KEY, '"grid"');
236+
});
214237
});

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

+45-4
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ import NoProjectMessage from 'sentry/components/noProjectMessage';
1919
import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
2020
import Pagination from 'sentry/components/pagination';
2121
import SearchBar from 'sentry/components/searchBar';
22+
import {SegmentedControl} from 'sentry/components/segmentedControl';
2223
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
2324
import Switch from 'sentry/components/switchButton';
24-
import {IconAdd} from 'sentry/icons';
25+
import {IconAdd, IconDashboard, IconList} from 'sentry/icons';
2526
import {t} from 'sentry/locale';
2627
import {space} from 'sentry/styles/space';
2728
import type {SelectValue} from 'sentry/types/core';
@@ -64,12 +65,23 @@ const SORT_OPTIONS: SelectValue<string>[] = [
6465
];
6566

6667
const SHOW_TEMPLATES_KEY = 'dashboards-show-templates';
68+
export const LAYOUT_KEY = 'dashboards-overview-layout';
69+
70+
const GRID = 'grid';
71+
const LIST = 'list';
72+
73+
type DashboardsLayout = 'grid' | 'list';
6774

6875
function shouldShowTemplates(): boolean {
6976
const shouldShow = localStorage.getItem(SHOW_TEMPLATES_KEY);
7077
return shouldShow === 'true' || shouldShow === null;
7178
}
7279

80+
function getDashboardsOverviewLayout(): DashboardsLayout {
81+
const dashboardsLayout = localStorage.getItem(LAYOUT_KEY);
82+
return dashboardsLayout === GRID || dashboardsLayout === LIST ? dashboardsLayout : GRID;
83+
}
84+
7385
function ManageDashboards() {
7486
const organization = useOrganization();
7587
const navigate = useNavigate();
@@ -81,6 +93,10 @@ function ManageDashboards() {
8193
SHOW_TEMPLATES_KEY,
8294
shouldShowTemplates()
8395
);
96+
const [dashboardsLayout, setDashboardsLayout] = useLocalStorageState(
97+
LAYOUT_KEY,
98+
getDashboardsOverviewLayout()
99+
);
84100
const [{rowCount, columnCount}, setGridSize] = useState({
85101
rowCount: DASHBOARD_GRID_DEFAULT_NUM_ROWS,
86102
columnCount: DASHBOARD_GRID_DEFAULT_NUM_COLUMNS,
@@ -230,13 +246,37 @@ function ManageDashboards() {
230246
function renderActions() {
231247
const activeSort = getActiveSort();
232248
return (
233-
<StyledActions>
249+
<StyledActions listView={organization.features.includes('dashboards-table-view')}>
234250
<SearchBar
235251
defaultQuery=""
236252
query={getQuery()}
237253
placeholder={t('Search Dashboards')}
238254
onSearch={query => handleSearch(query)}
239255
/>
256+
<Feature features={'organizations:dashboards-table-view'}>
257+
<SegmentedControl<DashboardsLayout>
258+
onChange={setDashboardsLayout}
259+
size="md"
260+
value={dashboardsLayout}
261+
aria-label={t('Layout Control')}
262+
>
263+
<SegmentedControl.Item
264+
key="grid"
265+
textValue="grid"
266+
aria-label={t('Grid View')}
267+
>
268+
{/* TODO (nikkikapadia): replace this icon with correct one once made */}
269+
<IconDashboard />
270+
</SegmentedControl.Item>
271+
<SegmentedControl.Item
272+
key="list"
273+
textValue="list"
274+
aria-label={t('List View')}
275+
>
276+
<IconList />
277+
</SegmentedControl.Item>
278+
</SegmentedControl>
279+
</Feature>
240280
<CompactSelect
241281
triggerProps={{prefix: t('Sort By')}}
242282
value={activeSort.value}
@@ -444,9 +484,10 @@ function ManageDashboards() {
444484
);
445485
}
446486

447-
const StyledActions = styled('div')`
487+
const StyledActions = styled('div')<{listView: boolean}>`
448488
display: grid;
449-
grid-template-columns: auto max-content;
489+
grid-template-columns: ${p =>
490+
p.listView ? 'auto max-content max-content' : 'auto max-content'};
450491
gap: ${space(2)};
451492
margin-bottom: ${space(2)};
452493

0 commit comments

Comments
 (0)