Skip to content

Commit ebca518

Browse files
committed
[ALS-6928] Add details drawer to dashboard (#302)
1 parent b41c517 commit ebca518

File tree

15 files changed

+226
-16
lines changed

15 files changed

+226
-16
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ VITE_USE_QUERY_TEMPLATE=true
3232
VITE_API=true
3333
VITE_DISCOVER=true
3434
VITE_DASHBOARD=true
35+
VITE_DASHBOARD_DRAWER=true
3536
VITE_ENABLE_SAMPLE_ID_CHECKBOX=true
3637

3738
# VITE_AUTH_PROVIDER_MODULE is the prefix for any authorization providers you want to use.

.env.test

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ VITE_DISCOVER=true
2323
VITE_DASHBOARD=true
2424
VITE_ENABLE_SNP_QUERY=true
2525
VITE_ENABLE_GENE_QUERY=true
26+
VITE_DASHBOARD_DRAWER=true
2627
VITE_ENABLE_SAMPLE_ID_CHECKBOX=true
2728

2829
# VITE_AUTH_PROVIDER_MODULE is the prefix for any authorization providers you want to use.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<script lang="ts">
2+
import { getDrawerStore } from '@skeletonlabs/skeleton';
3+
import { getDatasetDetails } from '$lib/services/dictionary';
4+
import type { DashboardRow } from '$lib/stores/Dashboard';
5+
import { ProgressRadial } from '@skeletonlabs/skeleton';
6+
import ErrorAlert from '$lib/components/ErrorAlert.svelte';
7+
const drawerStore = getDrawerStore();
8+
9+
const datasetId = (($drawerStore.meta.row as DashboardRow)?.dataset_id as string) || '';
10+
const title = (($drawerStore.meta.row as DashboardRow)?.name as string) || '';
11+
const link = (($drawerStore.meta.row as DashboardRow)?.additional_info_link as string) || '';
12+
13+
async function getDataset() {
14+
const details = await getDatasetDetails(datasetId);
15+
if (!details || Object.keys(details).length === 0) throw new Error('No details found');
16+
if (details.datasetId) {
17+
delete details.datasetId;
18+
}
19+
if (details.studyFullname) {
20+
delete details.studyFullname;
21+
}
22+
return details;
23+
}
24+
</script>
25+
26+
{#if title}
27+
<h2 data-testid="drawer-title" class="text-2xl font-bold ml-4">{title}</h2>
28+
{/if}
29+
<hr class="m-4 border-t-2 border-gray-200" />
30+
{#await getDataset()}
31+
<div class="flex justify-center items-center h-full">
32+
<ProgressRadial />
33+
</div>
34+
{:then details}
35+
<ul data-testid="drawer-details" class="m-4 p-4">
36+
{#each Object.entries(details) as [key, value]}
37+
{#if value}
38+
<li class="m-2">
39+
<strong class="capitalize"
40+
>{key
41+
.replace(/([A-Z])/g, ' $1')
42+
.toLowerCase()
43+
.trim()}</strong
44+
>:
45+
{#if Array.isArray(value)}
46+
<ul class="list-disc">
47+
{#each value as item}
48+
{#if item}
49+
<li class="ml-8">{item}</li>
50+
{/if}
51+
{/each}
52+
</ul>
53+
{:else}
54+
{value}
55+
{/if}
56+
</li>
57+
{/if}
58+
{/each}
59+
</ul>
60+
{#if link}
61+
<div class="flex justify-center items-center mb-4">
62+
<a
63+
href={link || '#'}
64+
on:click|stopPropagation
65+
class="btn variant-ghost-primary hover:variant-filled-primary"
66+
target="_blank">More Info</a
67+
>
68+
</div>
69+
{/if}
70+
{:catch}
71+
<div class="flex justify-center items-center">
72+
<ErrorAlert title="An Error Occured">
73+
<p>We're having trouble fetching the dataset details right now. Please try again later.</p>
74+
</ErrorAlert>
75+
</div>
76+
{/await}

src/lib/components/datatable/DashboardLink.svelte

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
{#if consentGranted}
88
<i class="fa-regular fa-circle-check text-3xl text-success-500"></i>
99
{:else}
10-
<a href={link} class="btn variant-ghost-primary hover:variant-filled-primary" target="_blank"
11-
>More Info</a
10+
<a
11+
href={link || '#'}
12+
title={link ? 'More Info' : 'Link not available'}
13+
on:click|stopPropagation={(e) => !link && e.preventDefault()}
14+
class="btn variant-ghost-primary hover:variant-filled-primary{!link
15+
? ' opacity-50 cursor-not-allowed'
16+
: ''}"
17+
target="_blank">More Info</a
1218
>
1319
{/if}

src/lib/components/datatable/RemoteTable.svelte

+10-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
export let columns: Column[] = [];
2626
export let cellOverides: Indexable = {};
2727
export let rowClickHandler: (row: Indexable) => void = () => {};
28-
28+
export let isClickable: boolean = false;
2929
let rows = handler.getRows();
3030
3131
onMount(() => {
@@ -100,7 +100,15 @@
100100
</tr>
101101
{:else if $rows.length > 0}
102102
{#each $rows as row, i}
103-
<ExpandableRow {tableName} {cellOverides} {columns} index={i} {row} {rowClickHandler} />
103+
<ExpandableRow
104+
{tableName}
105+
{cellOverides}
106+
{columns}
107+
index={i}
108+
{row}
109+
{rowClickHandler}
110+
{isClickable}
111+
/>
104112
{/each}
105113
{:else}
106114
<tr><td colspan={columns.length}>No entries found.</td></tr>

src/lib/components/datatable/Row.svelte

+7-3
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,23 @@
1515
export let index: number = -2;
1616
export let row: Indexable = {};
1717
export let tableName: string = '';
18+
export let isClickable: boolean = false;
1819
export let rowClickHandler: (row: Indexable) => void = () => {};
1920
2021
function onClick(row: Indexable) {
21-
setActiveRow({ row: row.conceptPath, table: tableName });
22+
setActiveRow({ row: row.conceptPath || row.dataset_id, table: tableName });
2223
rowClickHandler(row);
2324
}
24-
$: active = $activeTable === tableName && $activeRow === row?.conceptPath;
25+
$: active =
26+
$activeTable === tableName &&
27+
($activeRow === row?.conceptPath || $activeRow === row.dataset_id);
2528
</script>
2629

2730
<tr
2831
id="row-{index.toString()}"
2932
on:click|stopPropagation={() => onClick(row)}
30-
class="cursor-pointer"
33+
class={isClickable ? 'cursor-pointer' : ''}
34+
tabindex={isClickable ? 0 : -1}
3135
>
3236
{#each columns as column, colIndex}
3337
<td id="row-{index.toString()}-col-{colIndex.toString()}">

src/lib/components/datatable/Table.svelte

+10-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
export let stickyHeader = false;
2424
export let showPagination = true;
2525
export let rowClickHandler: (row: Indexable) => void = () => {};
26-
26+
export let isClickable: boolean = false;
2727
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2828
export let data: any = []; //TODO: Fix this type
2929
@@ -83,7 +83,15 @@
8383
<tbody>
8484
{#if $rows.length > 0}
8585
{#each $rows as row, i}
86-
<ExpandableRow {tableName} {cellOverides} {columns} index={i} {row} {rowClickHandler} />
86+
<ExpandableRow
87+
{tableName}
88+
{cellOverides}
89+
{columns}
90+
index={i}
91+
{row}
92+
{rowClickHandler}
93+
{isClickable}
94+
/>
8795
{/each}
8896
{:else}
8997
<tr><td colspan={columns.length}>No entries found.</td></tr>

src/lib/configuration.ts

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export const features: Indexable = {
139139
distributionExplorer: import.meta.env?.VITE_DIST_EXPLORER === 'true',
140140
},
141141
dashboard: import.meta.env?.VITE_DASHBOARD === 'true',
142+
dashboardDrawer: import.meta.env?.VITE_DASHBOARD_DRAWER === 'true',
142143
confirmDownload: import.meta.env?.VITE_CONFIRM_DOWNLOAD === 'true',
143144
};
144145

src/lib/services/dictionary.ts

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { user } from '$lib/stores/User';
1212
const dictionaryUrl = 'picsure/proxy/dictionary-api/';
1313
const searchUrl = 'picsure/proxy/dictionary-api/concepts';
1414
const conceptDetailUrl = 'picsure/proxy/dictionary-api/concepts/detail/';
15+
const datasetDetailUrl = 'picsure/proxy/dictionary-api/dashboard-drawer/';
1516

1617
export type FacetSkeleton = {
1718
[facetCategory: string]: string[];
@@ -146,3 +147,7 @@ export async function getStudiesCount(isOpenAccess = false) {
146147
const facetsForUser = facetCat.facets.filter((facet) => facet.count > 0);
147148
return facetsForUser.length;
148149
}
150+
151+
export async function getDatasetDetails(datasetId: string) {
152+
return api.get(`${datasetDetailUrl}${datasetId}`);
153+
}

src/routes/(picsure)/(authorized)/(admin)/admin/authorization/+page.svelte

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
cellOverides={roleTable.overrides}
8585
defaultRowsPerPage={10}
8686
rowClickHandler={roleRowCLick}
87+
isClickable={true}
8788
/>
8889
</div>
8990
<div id="authorization-privilege-table" class="mb-10">
@@ -106,6 +107,7 @@
106107
cellOverides={privilegesTable.overrides}
107108
defaultRowsPerPage={10}
108109
rowClickHandler={privilegeRowClick}
110+
isClickable={true}
109111
/>
110112
</div>
111113
{:catch}

src/routes/(picsure)/(authorized)/(admin)/admin/users/+page.svelte

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
defaultRowsPerPage={10}
110110
title={connection.label}
111111
{rowClickHandler}
112+
isClickable={true}
112113
/>
113114
</div>
114115
{/each}

src/routes/(picsure)/(public)/dashboard/+page.svelte

+14-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
import { ProgressBar } from '@skeletonlabs/skeleton';
66
7-
import { branding } from '$lib/configuration';
7+
import { branding, features } from '$lib/configuration';
88
import Content from '$lib/components/Content.svelte';
99
import Datatable from '$lib/components/datatable/Table.svelte';
1010
import DashboardLink from '$lib/components/datatable/DashboardLink.svelte';
@@ -14,6 +14,10 @@
1414
import type { Column } from '$lib/models/Tables';
1515
import type { DashboardRow } from '$lib/stores/Dashboard';
1616
17+
import { getDrawerStore } from '@skeletonlabs/skeleton';
18+
19+
const drawerStore = getDrawerStore();
20+
1721
const tableName = 'ExplorerTable';
1822
1923
let unsubColumns: Unsubscriber;
@@ -40,6 +44,13 @@
4044
unsubColumns && unsubColumns();
4145
unsubRows && unsubRows();
4246
});
47+
48+
function rowClickHandler(row: DashboardRow) {
49+
drawerStore.open({
50+
id: 'dashboard-drawer',
51+
meta: { row },
52+
});
53+
}
4354
</script>
4455

4556
<svelte:head>
@@ -59,6 +70,8 @@
5970
search={false}
6071
showPagination={false}
6172
stickyHeader={true}
73+
rowClickHandler={features.dashboardDrawer ? rowClickHandler : undefined}
74+
isClickable={features.dashboardDrawer}
6275
/>
6376
{/await}
6477
</section>

src/routes/(picsure)/+layout.svelte

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
<script lang="ts">
2-
import { AppShell, Modal, Toast, storePopup, type ModalComponent } from '@skeletonlabs/skeleton';
2+
import {
3+
AppShell,
4+
Modal,
5+
Toast,
6+
Drawer,
7+
storePopup,
8+
type ModalComponent,
9+
} from '@skeletonlabs/skeleton';
310
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
411
import Navigation from '$lib/components/Navigation.svelte';
512
import { onMount } from 'svelte';
@@ -8,12 +15,14 @@
815
import ExportStepper from '$lib/components/explorer/export/ExportStepper.svelte';
916
import Footer from '$lib/components/Footer.svelte';
1017
import ModalWrapper from '$lib/components/modals/ModalWrapper.svelte';
11-
import { getModalStore } from '@skeletonlabs/skeleton';
18+
import { getModalStore, getDrawerStore } from '@skeletonlabs/skeleton';
1219
import { beforeNavigate } from '$app/navigation';
1320
import { hasInvalidFilter, hasGenomicFilter, hasUnallowedFilter } from '$lib/stores/Filter.ts';
21+
import DashboardDrawer from '$lib/components/datatable/DashboardDrawer.svelte';
1422
import FilterWarning from '$lib/components/modals/FilterWarning.svelte';
1523
1624
const modalStore = getModalStore();
25+
const drawerStore = getDrawerStore();
1726
1827
// Highlight.js
1928
import hljs from 'highlight.js/lib/core';
@@ -68,6 +77,11 @@
6877

6978
<Toast position="t" />
7079
<Modal {...modalProps} />
80+
<Drawer position="right" width="w-1/2" rounded="rounded-none">
81+
{#if $drawerStore.id === 'dashboard-drawer'}
82+
<DashboardDrawer />
83+
{/if}
84+
</Drawer>
7185
<AppShell>
7286
<svelte:fragment slot="header">
7387
<Navigation />

tests/mock-data.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,24 @@ export const mockDashboard: DashboardResp = {
4545
{ label: 'Link', dataElement: 'additional_info_link' },
4646
],
4747
rows: [
48-
{ name: 'A', additional_info_link: 'foo.invalid' },
49-
{ name: 'B', additional_info_link: 'bar.invalid' },
48+
{
49+
name: 'A',
50+
description: 'This is a description 1',
51+
additional_info_link: 'foo.invalid',
52+
dataset_id: '1',
53+
},
54+
{
55+
name: 'B',
56+
description: 'This is a description 2',
57+
additional_info_link: 'bar.invalid',
58+
dataset_id: '2',
59+
},
60+
{
61+
name: 'C',
62+
description: 'This is a description 3',
63+
additional_info_link: null,
64+
dataset_id: '3',
65+
},
5066
],
5167
};
5268

0 commit comments

Comments
 (0)