Skip to content

Commit d55ccab

Browse files
committed
Improve resource navigation and clarity with visual indicators and smart pluralization
- Study View: Added visual indicators (user icon for Patient View links, external link icon for new window links) to clarify navigation destinations - Dynamic column headers that adapt based on resource type (e.g., "Pathology Reports per Patient") - Patient View: Simplified table by removing URL/Description columns, added resource counter (e.g., "1 of 3") - Consistent pluralization of resource names across tabs, headers, and metadata
1 parent 01fff90 commit d55ccab

File tree

5 files changed

+83
-32
lines changed

5 files changed

+83
-32
lines changed

src/pages/patientView/PatientViewPage.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { IColumnVisibilityDef } from 'shared/components/columnVisibilityControls
66
import {
77
DefaultTooltip,
88
toggleColumnVisibility,
9+
pluralize,
910
} from 'cbioportal-frontend-commons';
1011
import {
1112
PatientViewPageStore,
@@ -479,15 +480,19 @@ export class PatientViewPageInner extends React.Component<
479480
const tabs: JSX.Element[] = sorted.reduce((list, def) => {
480481
const data = resourceDataById[def.resourceId];
481482
if (data && data.length > 0) {
483+
const displayName =
484+
data.length > 1
485+
? pluralize(def.displayName, data.length)
486+
: def.displayName;
482487
list.push(
483488
<MSKTab
484489
key={getPatientViewResourceTabId(def.resourceId)}
485490
id={getPatientViewResourceTabId(def.resourceId)}
486-
linkText={def.displayName}
491+
linkText={displayName}
487492
onClickClose={this.closeResourceTab}
488493
>
489494
<ResourceTab
490-
resourceDisplayName={def.displayName}
495+
resourceDisplayName={displayName}
491496
resourceData={resourceDataById[def.resourceId]}
492497
urlWrapper={this.urlWrapper}
493498
/>

src/pages/studyView/StudyViewPage.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getBrowserWindow,
2222
onMobxPromise,
2323
remoteData,
24+
pluralize,
2425
} from 'cbioportal-frontend-commons';
2526
import { PageLayout } from '../../shared/components/PageLayout/PageLayout';
2627
import IFrameLoader from '../../shared/components/iframeLoader/IFrameLoader';
@@ -380,7 +381,10 @@ export default class StudyViewPage extends React.Component<
380381
}
381382

382383
@computed get shouldShowResources() {
383-
if (this.store.resourceDefinitions.isComplete) {
384+
if (
385+
this.store.resourceDefinitions.isComplete &&
386+
this.store.resourceIdToResourceData.isComplete
387+
) {
384388
return this.store.resourceDefinitions.result.length > 0;
385389
} else {
386390
return false;
@@ -542,17 +546,21 @@ export default class StudyViewPage extends React.Component<
542546
const tabs: JSX.Element[] = sorted.reduce((list, def) => {
543547
const data = resourceDataById[def.resourceId];
544548
if (data && data.length > 0) {
549+
const displayName =
550+
data.length > 1
551+
? pluralize(def.displayName, data.length)
552+
: def.displayName;
545553
list.push(
546554
<MSKTab
547555
key={getStudyViewResourceTabId(def.resourceId)}
548556
id={getStudyViewResourceTabId(def.resourceId)}
549-
linkText={def.displayName}
557+
linkText={displayName}
550558
onClickClose={this.closeResourceTab}
551559
>
552560
<ResourceTab
553561
resourceData={resourceDataById[def.resourceId]}
554562
urlWrapper={this.urlWrapper}
555-
resourceDisplayName={def.displayName}
563+
resourceDisplayName={displayName}
556564
/>
557565
</MSKTab>
558566
);
@@ -728,8 +736,13 @@ export default class StudyViewPage extends React.Component<
728736
linkText={
729737
this.store.resourceDefinitions
730738
.result?.length == 1
731-
? this.store.resourceDefinitions
732-
.result[0].displayName
739+
? pluralize(
740+
this.store
741+
.resourceDefinitions
742+
.result[0]
743+
.displayName,
744+
2
745+
)
733746
: RESOURCES_TAB_NAME
734747
}
735748
hide={!this.shouldShowResources}
@@ -740,10 +753,13 @@ export default class StudyViewPage extends React.Component<
740753
this.store
741754
.resourceDefinitions
742755
.result?.length == 1
743-
? this.store
744-
.resourceDefinitions
745-
.result[0]
746-
.displayName
756+
? pluralize(
757+
this.store
758+
.resourceDefinitions
759+
.result[0]
760+
.displayName,
761+
2
762+
)
747763
: RESOURCES_TAB_NAME
748764
}
749765
store={this.store}

src/pages/studyView/resources/FilesAndLinks.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,23 @@ export class FilesAndLinks extends React.Component<IFilesLinksTable, {}> {
241241
});
242242

243243
@computed get columns() {
244+
// Determine if there's only one unique resource type
245+
const uniqueResourceTypes =
246+
this.resourceData.result && this.resourceData.result.data
247+
? _.uniq(
248+
this.resourceData.result.data.map(
249+
item => item.typeOfResource as string
250+
)
251+
)
252+
: [];
253+
const resourcesPerPatientColumnName =
254+
uniqueResourceTypes.length === 1 && uniqueResourceTypes[0]
255+
? `${pluralize(
256+
uniqueResourceTypes[0] as string,
257+
2
258+
)} per Patient`
259+
: 'Resources per Patient';
260+
244261
let defaultColumns: Column<{ [id: string]: any }>[] = [
245262
{
246263
...this.getDefaultColumnConfig('patientId', 'Patient ID'),
@@ -293,7 +310,16 @@ export class FilesAndLinks extends React.Component<IFilesLinksTable, {}> {
293310
path,
294311
data.patientId
295312
)}
313+
style={{ fontSize: 10 }}
296314
>
315+
<i
316+
className={`fa fa-user fa-sm`}
317+
style={{
318+
marginRight: 5,
319+
color: 'black',
320+
}}
321+
title="Open in Patient View"
322+
/>
297323
{data.typeOfResource}
298324
</a>
299325
</div>
@@ -335,7 +361,7 @@ export class FilesAndLinks extends React.Component<IFilesLinksTable, {}> {
335361
{
336362
...this.getDefaultColumnConfig(
337363
'resourcesPerPatient',
338-
'Resources per Patient',
364+
resourcesPerPatientColumnName,
339365
true
340366
),
341367
render: (data: { [id: string]: number }) => {
@@ -369,11 +395,7 @@ export class FilesAndLinks extends React.Component<IFilesLinksTable, {}> {
369395
this.resourceData.result
370396
?.totalItems
371397
}{' '}
372-
{pluralize(
373-
this.props.resourceDisplayName,
374-
this.resourceData.result
375-
?.totalItems || 1
376-
)}
398+
{this.props.resourceDisplayName}
377399
</strong>
378400
</div>
379401
}

src/shared/components/resources/ResourceTab.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,7 @@ export default class ResourceTab extends React.Component<
118118
<div>
119119
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
120120
<FeatureTitle
121-
title={
122-
this.currentResourceDatum.resourceDefinition
123-
.displayName
124-
}
121+
title={this.props.resourceDisplayName}
125122
isLoading={false}
126123
className="pull-left"
127124
style={{ marginBottom: 10 }}
@@ -130,9 +127,10 @@ export default class ResourceTab extends React.Component<
130127
{this.currentResourceDatum.sampleId
131128
? this.currentResourceDatum.sampleId
132129
: this.currentResourceDatum.patientId}
133-
{this.currentResourceDatum.resourceDefinition
134-
.description &&
135-
` | ${this.currentResourceDatum.resourceDefinition.description}`}
130+
{` | ${this.props.resourceDisplayName}`}
131+
{` | ${this.currentResourceIndex + 1} of ${
132+
this.props.resourceData.length
133+
}`}
136134
</p>
137135
</div>
138136
<div

src/shared/components/resources/ResourceTable.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useLocalObservable } from 'mobx-react-lite';
66
import LazyMobXTable, {
77
Column,
88
} from 'shared/components/lazyMobXTable/LazyMobXTable';
9+
import _ from 'lodash';
910

1011
export interface IResourceTableProps {
1112
resources: ResourceData[];
@@ -102,11 +103,18 @@ const ResourceTable = observer(
102103
});
103104
}
104105

106+
// Determine if there's only one unique resource type
107+
const uniqueResourceNames = _.uniq(state.data.map(d => d.resourceName));
108+
const resourceColumnName =
109+
uniqueResourceNames.length === 1 && uniqueResourceNames[0]
110+
? uniqueResourceNames[0]
111+
: 'Resource';
112+
105113
columns.push(
106114
{
107-
name: 'Resource',
115+
name: resourceColumnName,
108116
headerRender: () => (
109-
<span data-test={'Resource'}>{'Resource'}</span>
117+
<span data-test={'Resource'}>{resourceColumnName}</span>
110118
),
111119
render: row => (
112120
<a onClick={() => openResource(row.resource)}>
@@ -123,8 +131,10 @@ const ResourceTable = observer(
123131
.includes(filterStringUpper ?? ''),
124132
},
125133
{
126-
name: '',
127-
headerRender: () => <span></span>,
134+
name: 'Resource URL',
135+
headerRender: () => (
136+
<span data-test={'Resource URL'}>{'Resource URL'}</span>
137+
),
128138
render: row => (
129139
<a
130140
href={row.url}
@@ -148,11 +158,11 @@ const ResourceTable = observer(
148158
headerRender: () => (
149159
<span data-test={'Description'}>{'Description'}</span>
150160
),
151-
render: row => <span>{row.description}</span>,
152-
download: row => row.description || '',
153-
sortBy: row => row.description || '',
161+
render: row => <span>{row.description ?? ''}</span>,
162+
download: row => row.description ?? '',
163+
sortBy: row => row.description ?? '',
154164
filter: (row, _filterString, filterStringUpper) =>
155-
(row.description || '')
165+
(row.description ?? '')
156166
.toUpperCase()
157167
.includes(filterStringUpper ?? ''),
158168
}

0 commit comments

Comments
 (0)