Skip to content

Commit

Permalink
Topic category UI elements (#244)
Browse files Browse the repository at this point in the history
* feat: create topic category tags

* feat: use real data when showing topic category tags

* feat: add topic category data to resource page

* feat: associate topic category with a Chakra UI colour

* feat: sort topic categories alphabetically on resource page

* chore: make icon optional in metadata block

* chore: make icon optional in metadata tag + add glyph property to metadata content

* feat: add EDAM onthology links to resource page

* chore: remove unneeded code

* refactor: remove object added to schema definitions file

* refactor: send data.topicCategory to TopicCategory component

* fix: filter out objects without topic name

* fix: ensure only topics with name are shown on resource page

* feat: add link to search to topic category tags

* fix: show empty state when taxonomy is missing name

---------

Co-authored-by: candicecz <[email protected]>
  • Loading branch information
leandrocollares and candicecz authored Jun 5, 2024
1 parent 872f9f8 commit 32c4c5c
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/components/icon/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export const getMetadataTheme = (property?: string) => {
return 'orange';
} else if (property?.toLowerCase() === 'species') {
return 'green';
} else if (property?.toLowerCase() === 'topiccategory') {
return 'teal';
} else {
return 'gray';
}
Expand Down
117 changes: 97 additions & 20 deletions src/components/metadata/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const SORT_ORDER = [
'funding',
'license',
'usageInfo',
'topicCategory',
];

// Sorts an array of metadata objects based on the [SORT_ORDER] defined above. Prioritizes the enabled items over the disabled items.
Expand Down Expand Up @@ -120,6 +121,13 @@ export const generateMetadataContent = (
);
case 'species':
return createSpeciesContent(id, property, data?.species, showItems);
case 'topicCategory':
return createTopicCategoryContent(
id,
property,
data?.topicCategory,
showItems,
);
case 'usageInfo':
return createUsageInfoContent(id, property, data?.usageInfo, showItems);
case 'variableMeasured':
Expand Down Expand Up @@ -222,14 +230,16 @@ const createHealthConditionContent = (
label: 'Health Condition',
property,
glyph: property,
isDisabled: !healthCondition,
isDisabled: !healthCondition || healthCondition.every(item => !item.name),
items:
showItems && healthCondition
? healthCondition.map((healthCondition, idx) => {
const name = Array.isArray(healthCondition.name)
? healthCondition.name.join(', ')
: healthCondition.name;

const termSet = healthCondition?.inDefinedTermSet?.toLowerCase();

return {
key: uniqueId(`${property}-${id}-${idx}`),
name,
Expand All @@ -242,12 +252,13 @@ const createHealthConditionContent = (
: healthCondition.name,
},
ontologyProps: {
['aria-label']:
healthCondition?.inDefinedTermSet?.toLowerCase() === 'other'
['aria-label']: termSet
? termSet === 'other'
? 'See term information in OLS.'
: `See ${healthCondition?.inDefinedTermSet} ontology information.`,
: `See ${healthCondition.inDefinedTermSet} ontology information.`
: 'See taxonomy information.',
value: healthCondition?.url,
label: `${healthCondition?.inDefinedTermSet}`,
label: healthCondition?.inDefinedTermSet,
inDefinedTermSet: healthCondition?.inDefinedTermSet,
},
};
Expand Down Expand Up @@ -292,7 +303,8 @@ const createMeasurementTechniqueContent = (
label: 'Measurement Technique',
property,
glyph: property,
isDisabled: !measurementTechnique,
isDisabled:
!measurementTechnique || measurementTechnique.every(item => !item.name),
items:
showItems && measurementTechnique
? measurementTechnique.map((measurementTechnique, idx) => {
Expand Down Expand Up @@ -332,7 +344,7 @@ const createInfectiousAgentContent = (
label: 'Pathogen',
property,
glyph: property,
isDisabled: !infectiousAgent,
isDisabled: !infectiousAgent || infectiousAgent.every(item => !item.name),
items:
showItems && infectiousAgent
? infectiousAgent.map((pathogen, idx) => {
Expand All @@ -344,11 +356,14 @@ const createInfectiousAgentContent = (
? pathogen.commonName.join(', ')
: pathogen.commonName;

const ontologyLabel = `${pathogen?.inDefinedTermSet}${
pathogen?.inDefinedTermSet?.toLowerCase() === 'uniprot'
? ' Taxon'
: ''
}`;
const termSet = pathogen?.inDefinedTermSet;

const ontologyLabel = termSet
? `${pathogen?.inDefinedTermSet}${
termSet?.toLowerCase() === 'uniprot' ? ' Taxon' : ''
}`
: '';

return {
key: uniqueId(`${property}-${id}-${idx}`),
name,
Expand All @@ -361,7 +376,9 @@ const createInfectiousAgentContent = (
: pathogen.name,
},
ontologyProps: {
['aria-label']: `See ${pathogen?.inDefinedTermSet} taxonomy information.`,
['aria-label']: termSet
? `See ${pathogen?.inDefinedTermSet} taxonomy information.`
: 'See taxonomy information.',
inDefinedTermSet: pathogen?.inDefinedTermSet,
label: ontologyLabel,
value: pathogen?.url,
Expand All @@ -384,7 +401,7 @@ const createSpeciesContent = (
label: 'Species',
property,
glyph: property,
isDisabled: !species,
isDisabled: !species || species.every(item => !item.name),
items:
showItems && species
? species.map((species, idx) => {
Expand All @@ -396,11 +413,14 @@ const createSpeciesContent = (
? species.commonName.join(', ')
: species.commonName;

const ontologyLabel = `${species?.inDefinedTermSet}${
species?.inDefinedTermSet?.toLowerCase() === 'uniprot'
? ' Taxon'
: ''
}`;
const termSet = species?.inDefinedTermSet;
const ontologyLabel = termSet
? `${species?.inDefinedTermSet}${
species?.inDefinedTermSet?.toLowerCase() === 'uniprot'
? ' Taxon'
: ''
}`
: '';

return {
key: uniqueId(`${property}-${id}-${idx}`),
Expand All @@ -414,7 +434,9 @@ const createSpeciesContent = (
: species.name,
},
ontologyProps: {
['aria-label']: `See ${species?.inDefinedTermSet} taxonomy information.`,
['aria-label']: termSet
? `See ${species?.inDefinedTermSet} taxonomy information.`
: 'See taxonomy information.',
label: ontologyLabel,
inDefinedTermSet: species?.inDefinedTermSet,
value: species?.url,
Expand All @@ -425,6 +447,61 @@ const createSpeciesContent = (
};
};

// Generates content specific to topic categories.
const createTopicCategoryContent = (
id: FormattedResource['id'],
property: string,
topicCategory?: FormattedResource['topicCategory'],
showItems = true,
) => {
// Sorts topic categories alphabetically
if (
topicCategory != null &&
topicCategory
?.filter(item => item.name !== undefined)
.every(item => typeof item.name === 'string')
)
topicCategory.sort((a, b) =>
(a.name as string).localeCompare(b.name as string),
);

return {
id: `${property}-${id}`,
label: 'Topic Category',
property,
isDisabled: !topicCategory || topicCategory.every(item => !item.name),
items:
showItems && topicCategory
? topicCategory.map((topicCategory, idx) => {
const name = Array.isArray(topicCategory.name)
? topicCategory.name.join(', ')
: topicCategory.name;
const termSet = topicCategory?.inDefinedTermSet;

return {
key: uniqueId(`${property}-${id}-${idx}`),
name,
searchProps: {
['aria-label']: `Search for results with topic category "${name}"`,
property: 'topicCategory.name',
value: Array.isArray(topicCategory.name)
? topicCategory.name.join('" OR "')
: topicCategory.name,
},
ontologyProps: {
['aria-label']: termSet
? `See ${topicCategory?.inDefinedTermSet} taxonomy information.`
: 'See taxonomy information.',
label: topicCategory?.inDefinedTermSet,
inDefinedTermSet: topicCategory?.inDefinedTermSet,
value: topicCategory?.url,
},
};
})
: [],
};
};

// Generates content specific to usage info.
const createUsageInfoContent = (
id: FormattedResource['id'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const Overview: React.FC<OverviewProps> = ({
measurementTechnique,
spatialCoverage,
species,
topicCategory,
temporalCoverage,
variableMeasured,
...data
Expand All @@ -46,6 +47,7 @@ const Overview: React.FC<OverviewProps> = ({
healthCondition,
variableMeasured,
measurementTechnique,
topicCategory,
});
const sortedMetadataContent = sortMetadataArray(
[
Expand Down
1 change: 1 addition & 0 deletions src/components/resource-sections/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const sectionMetadata: { [key: string]: (keyof FormattedResource)[] } = {
'spatialCoverage',
'species',
'temporalCoverage',
'topicCategory',
'variableMeasured',
],
softwareInformation: [
Expand Down
6 changes: 6 additions & 0 deletions src/components/search-results-page/components/card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { FaCircleArrowRight, FaAngleRight, FaRegClock } from 'react-icons/fa6';
import { FormattedResource } from 'src/utils/api/types';
import { TypeBanner } from 'src/components/resource-sections/components';
import MetadataAccordion from './metadata-accordion';
import TopicCategory from './topic-category';
import { DisplayHTMLContent } from 'src/components/html-content';
import { AccessibleForFree, ConditionsOfAccess } from 'src/components/badges';
import { SourceLogo, getSourceDetails } from './source-logo';
Expand Down Expand Up @@ -278,6 +279,11 @@ const SearchResultCard: React.FC<SearchResultCardProps> = ({
</Stack>

<MetadataAccordion data={data} />

{data?.topicCategory && data?.topicCategory.length > 0 && (
<TopicCategory data={data.topicCategory} />
)}

<Stack
flex={1}
p={1}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import NextLink from 'next/link';
import { Stack, Flex, Tag, TagLabel, TagLeftIcon } from '@chakra-ui/react';
import { TopicCategory } from 'src/utils/api/types';
import { encodeString } from 'src/utils/querystring-helpers';
import { FaMagnifyingGlass } from 'react-icons/fa6';

interface TopicCategoryProps {
data?: TopicCategory[] | null;
}

const TopicCategories: React.FC<TopicCategoryProps> = ({ data }) => {
const topicCategoryNames = data
?.filter(element => element.name !== undefined)
.map(element => element.name!)
.sort();
const paddingCard = [4, 6, 8, 10];
if (topicCategoryNames?.length === 0) return null;
return (
<Stack
my='0'
px={paddingCard}
py='2'
borderBottom='1px solid'
borderBottomColor='gray.200'
>
<Flex flexWrap='wrap'>
{topicCategoryNames?.map(name => {
return (
<NextLink
key={name}
href={{
pathname: '/search',
query: {
q: `topicCategory.name:"${encodeString(name.trim())}"`,
advancedSearch: true,
},
}}
>
<Tag
size='sm'
variant='subtle'
m='0.5'
colorScheme='primary'
cursor='pointer'
_hover={{ textDecoration: 'underline' }}
>
<TagLeftIcon as={FaMagnifyingGlass} />
<TagLabel>{name}</TagLabel>
</Tag>
</NextLink>
);
})}
</Flex>
</Stack>
);
};

export default TopicCategories;
1 change: 1 addition & 0 deletions src/components/search-results-page/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ const SearchResultsPage = ({
'name',
'sdPublisher',
'species',
'topicCategory',
'url',
'usageInfo',
'variableMeasured',
Expand Down
3 changes: 2 additions & 1 deletion src/utils/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,15 @@ interface TemporalCoverage {
};
}

interface TopicCategory {
export interface TopicCategory {
description?: string;
name?: string;
url?: string;
curatedBy?: {
name?: string;
url?: string;
};
inDefinedTermSet?: string;
}

export interface InputProperties {
Expand Down

0 comments on commit 32c4c5c

Please sign in to comment.