Skip to content

Commit

Permalink
Merge pull request #3837 from molgenis/feat/add_studies_in_biobank_di…
Browse files Browse the repository at this point in the history
…rectory

feat(directory): add studies associated to collections
  • Loading branch information
svituz authored Oct 22, 2024
2 parents f38b85a + ab775d9 commit a5e8310
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,32 @@
</li>
</ul>
</template>
<template v-if="info.studies && info.studies.length > 0">
<h5>Studies</h5>
<ul class="right-content-list">
<li>
<div
class="info-list"
v-for="(study, index) in info.studies"
:key="`${study.id}-${index}`"
>
<span class="font-weight-bold mr-2">Name:</span>
<span>{{ study.title }}</span>
<div>
<span
class="fa fa-fw fa-address-card mr-2"
aria-hidden="true"
/>
<router-link :to="study.report">
<span>
{{ uiText["view"] }} {{ study.title }} study
</span>
</router-link>
</div>
</div>
</li>
</ul>
</template>
<template v-if="info.collaboration.length > 0">
<h5>Collaboration</h5>
<div class="container p-0">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<template>
<div>
<div class="d-flex">
<report-description
:description="study.description"
:maxLength="500"
></report-description>
</div>

<!-- study information -->
<view-generator :viewmodel="studyModel.viewmodel" />
</div>
</template>

<script>
import { getStudyDetails } from "../../functions/viewmodelMapper";
import ReportDescription from "../report-components/ReportDescription.vue";
import ViewGenerator from "../generators/ViewGenerator.vue";
export default {
name: "ReportStudyDetails",
props: {
study: {
type: Object,
required: true,
},
},
components: {
ReportDescription,
ViewGenerator,
},
computed: {
studyModel() {
return this.study ? getStudyDetails(this.study) : {};
},
},
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="card-text">
<template v-if="info.also_known">
<h5>Also Known In</h5>
<ReportDetailsList :reportDetails="info.also_known" />
</template>
</div>
</div>
</div>
</div>
</template>

<script setup>
import { toRefs } from "vue";
import ReportDetailsList from "../../components/report-components/ReportDetailsList.vue";
const props = defineProps(["info"]);
const { info } = toRefs(props);
</script>

<style scoped>
.right-content-list {
list-style-type: none;
margin-left: -2.5rem;
}
.right-content-list:not(:last-child) {
margin-bottom: 1.5rem;
}
.right-content-list li {
margin-bottom: 0.5rem;
}
.info-list {
margin-bottom: 1rem;
}
.cert-badge:not(:last-child) {
margin-right: 1rem;
}
</style>
28 changes: 28 additions & 0 deletions apps/directory/src/functions/viewmodelMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ export function getCollectionDetails(collection, isBiobankWithdrawn) {
};
}

export function getStudyDetails(study) {
const settingsStore = useSettingsStore();
const viewmodel = getViewmodel(study, settingsStore.config.studyColumns);

return {
...study,
viewmodel,
};
}

export const getBiobankDetails = (biobank) => {
const settingsStore = useSettingsStore();

Expand Down Expand Up @@ -336,6 +346,16 @@ export const collectionReportInformation = (collection) => {
collectionReport.certifications = mapQualityStandards(collection.quality);
}

if (collection.studies) {
collectionReport.studies = collection.studies.map((study) => {
return {
id: study.id,
title: study.title,
report: `/study/${study.id}`,
};
});
}

collectionReport.collaboration = [];

if (collection.collaboration_commercial) {
Expand All @@ -357,6 +377,14 @@ export const collectionReportInformation = (collection) => {
return collectionReport;
};

export const getStudyReportInformation = (study) => {
const studyReport = {};

studyReport.also_known = study.also_known ? mapAlsoKnownIn(study) : undefined;

return studyReport;
};

export const mapNetworkInfo = (data) => {
return data.network.map((network) => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ const initialCollectionColumns = [
"sub_collections.withdrawn",
"collaboration_commercial",
"collaboration_non_for_profit",
"studies.id",
"studies.title",
...ContactInfoColumns,
...HeadInfoColumns,
],
Expand Down
23 changes: 23 additions & 0 deletions apps/directory/src/property-config/initialStudyColumns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const initialStudyColumns = [
{ label: "Id:", column: "id", type: "string", showCopyIcon: true },
{ label: "Title:", column: "title", type: "string" },
{ label: "Description:", column: "description", type: "string" },
{ label: "Type:", column: "type", type: "string" },
{ label: "Sex:", column: { sex: ["label"] }, type: "array" },
{ label: "Number of subjects:", column: "number_of_subjects", type: "int" },
{
label: "Age:",
type: "range",
min: "age_high",
max: "age_low",
unit: "age_unit",
unit_column: { age_unit: ["label"] },
},
{
label: "Also Known In:",
column: { also_known: ["name_system", "url"] },
type: "object",
},
];

export default initialStudyColumns;
6 changes: 6 additions & 0 deletions apps/directory/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Landingpage from "../views/Landingpage.vue";
import BiobankReport from "../views/BiobankReport.vue";
import NetworkReport from "../views/NetworkReport.vue";
import CollectionReport from "../views/CollectionReport.vue";
import StudyReport from "../views/StudyReport.vue";
import ConfigurationScreen from "../views/ConfigurationScreen.vue";
import { useSettingsStore } from "../stores/settingsStore";

Expand Down Expand Up @@ -32,6 +33,11 @@ const router = createRouter({
component: BiobankReport,
},
{ path: "/network/:id", name: "networkdetails", component: NetworkReport },
{
path: "/study/:id",
name: "studydetails",
component: StudyReport,
},
{
path: "/configuration",
component: ConfigurationScreen,
Expand Down
2 changes: 2 additions & 0 deletions apps/directory/src/stores/settingsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import initialBiobankColumns from "../property-config/initialBiobankColumns";
import initialBiobankReportColumns from "../property-config/initialBiobankReportColumns";
import initialLandingpage from "../property-config/initialLandingpage";
import { QueryEMX2 } from "molgenis-components";
import initialStudyColumns from "../property-config/initialStudyColumns";
/**
* Settings store is where all the configuration of the application is handled.
* This means that user config from the database is merged with the defaults here.
Expand All @@ -27,6 +28,7 @@ export const useSettingsStore = defineStore("settingsStore", () => {
biobankColumns: initialBiobankColumns,
biobankReportColumns: initialBiobankReportColumns,
collectionColumns: initialCollectionColumns,
studyColumns: initialStudyColumns,
filterFacets: initialFilterFacets,
filterMenuInitiallyFolded: false,
biobankCardShowCollections: true,
Expand Down
41 changes: 41 additions & 0 deletions apps/directory/src/stores/studyStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineStore } from "pinia";
//@ts-ignore
import { QueryEMX2 } from "molgenis-components";
import { useSettingsStore } from "./settingsStore";

export const useStudyStore = defineStore("studyStore", () => {
const settingsStore = useSettingsStore();

const studyColumns = settingsStore.config.studyColumns;
const graphqlEndpoint = settingsStore.config.graphqlEndpoint;

function getStudyColumns() {
const properties = studyColumns
.filter((column) => column.column)
.flatMap((studyColumn) => studyColumn.column);

const rangeProperties = studyColumns.filter(
(column) => column.type === "range"
);
for (const property of rangeProperties) {
properties.push(property.min, property.max, property.unit_column);
}
return properties;
}

async function getStudyReport(id: string) {
const studyReportQuery = new QueryEMX2(graphqlEndpoint)
.table("Studies")
.select(getStudyColumns())
.orderBy("Studies", "id", "asc")
.where("id")
.like(id);
const reportResults = await studyReportQuery.execute();

return reportResults;
}

return {
getStudyReport,
};
});
77 changes: 77 additions & 0 deletions apps/directory/src/views/StudyReport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div class="container mg-network-report-card">
<div
v-if="!loaded"
class="d-flex justify-content-center align-items-center spinner-container"
>
<Spinner />
</div>
<div v-else class="container-fluid">
<div class="row">
<div class="col my-3 shadow-sm d-flex p-2 align-items-center bg-white">
<Breadcrumb
class="directory-nav"
:crumbs="{
[uiText['home']]: '../#/',
[study.title]: '/',
}"
/>
</div>
</div>

<div class="row" v-if="study">
<div class="col">
<report-title type="Study" :name="study.title" />
<div class="container">
<div class="row">
<div class="container p-0">
<div class="row">
<div class="col-md-8">
<report-study-details v-if="study" :study="study" />
</div>
<study-report-info-card :info="info" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script setup>
import { Breadcrumb, Spinner } from "molgenis-components";
import { computed, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import ReportStudyDetails from "../components/report-components/ReportStudyDetails.vue";
import StudyReportInfoCard from "../components/report-components/StudyReportInfoCard.vue";
import ReportTitle from "../components/report-components/ReportTitle.vue";
import { getStudyReportInformation } from "../functions/viewmodelMapper";
import { useStudyStore } from "../stores/studyStore";
import { useSettingsStore } from "../stores/settingsStore";
const settingsStore = useSettingsStore();
const studyStore = useStudyStore();
const route = useRoute();
const study = ref({});
let loaded = ref(false);
loadStudyReport(route.params.id);
const uiText = computed(() => settingsStore.uiText);
const info = computed(() => {
return getStudyReportInformation(study.value);
});
function loadStudyReport(id) {
loaded.value = false;
studyStore.getStudyReport(id).then((result) => {
study.value = result.Studies.length ? result.Studies[0] : {};
loaded.value = true;
});
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void test08DataCatalogueNetworkStagingLoader() {
public void test09DirectoryLoader() {
Schema directory = database.createSchema(DIRECTORY_TEST);
DataModels.Regular.BIOBANK_DIRECTORY.getImportTask(directory, true).run();
assertEquals(10, directory.getTableNames().size());
assertEquals(11, directory.getTableNames().size());
}

@Test
Expand Down
3 changes: 2 additions & 1 deletion data/biobank-directory/demo/AlsoKnownIn.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
id,name_system,pid,url,national_node,label
bbmri-eric:akiID:NL_00001,Catalogue in The Netherlands,cat_neth_biobank2,https://linktopid/cat_neth_biobank2.nl,NL,Catalogue in The Netherlands
bbmri-eric:akiID:NL_00002,Collection catalogue of The Netherlands,,https://collections@catalogue.nl,NL,Collection catalogue of The Netherlands
bbmri-eric:akiID:DE_00001,Another DataCatalogue,,https://another_data_catalogue.eu,DE,Another DataCatalogue
bbmri-eric:akiID:DE_00001,Another DataCatalogue,,https://another_data_catalogue.eu,DE,Another DataCatalogue
bbmri-eric:akiID:DE_00002,A catalogue of studies,,https://a_study_catalogue.eu/112233,EXT,A Catalogue With Studies
8 changes: 4 additions & 4 deletions data/biobank-directory/demo/Collections.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
id,name,acronym,description,url,location,country,latitude,longitude,head,contact,national_node,withdrawn,parent_collection,biobank,biobank_label,network,combined_network,also_known,type,data_categories,order_of_magnitude,size,categories,timestamp,number_of_donors,order_of_magnitude_donors,sex,diagnosis_available,age_low,age_high,age_unit,materials,storage_temperatures,body_part_examined,imaging_modality,image_dataset_type,collaboration_commercial,collaboration_non_for_profit,data_use,commercial_use,access_fee,access_joint_project,access_description,access_uri,sop,combined_quality
bbmri-eric:ID:DE_biobank1:collection:coll1,Collection1 of biobank1,coll1,The first collection of biobank1,,Berlin,DE,,,bbmri-eric:contactID:DE_collection1,bbmri-eric:contactID:DE_collection1,DE,false,,bbmri-eric:ID:DE_biobank1,Biobank1,bbmri-eric:networkID:DE_nw_coll1,"bbmri-eric:networkID:DE_network1,bbmri-eric:networkID:DE_nw_coll1",bbmri-eric:akiID:DE_00001,"COHORT,RD","MEDICAL_RECORDS,BLOOD",3,1333,"rare_disease,oncology",,600,2,"MALE,NEUTERED_MALE","ORPHA:100001,ORPHA:101009",2,88,YEAR,"DNA,PERIPHERAL_BLOOD_CELLS",temperature2to10,T-04000,BDUS,1.2.840.10008.5.1.4.1.1.104.2,true,true,DUO_0000003,true,"samples,data,images","data,images",,,"sample_transport_sop,data_processing_sop",accredited
bbmri-eric:ID:NL_biobank2:collection:coll2,The second collection of biobank2,coll2_bb2,This is the description of the second collection of biobank2,,Amsterdam,NL,,,bbmri-eric:contactID:head_coll2,bbmri-eric:contactID:NL_person1,NL,false,,bbmri-eric:ID:NL_biobank2,Biobank2,bbmri-eric:networkID:EU_network,bbmri-eric:networkID:EU_network,bbmri-eric:akiID:NL_00001,"CASE_CONTROL,QUALITY_CONTROL","BIOLOGICAL_SAMPLES,IMAGING_DATA,NATIONAL_REGISTRIES",4,11000,"covid19,paediatrics,autoimmune",,9000,3,FEMALE,"urn:miriam:icd:W00,ORPHA:3000",0,90,YEAR,"FECES,PATHOGEN,URINE",temperatureLN,T-58200,CR,1.2.840.10008.5.1.4.1.1.130,true,true,DUO_0000042,false,"samples,data,images","samples,data,images",Access is possible need to pay a fee,,"sample_processing_sop,sample_transport_sop,data_transport_sop,data_storage_sop",accredited
bbmri-eric:ID:NL_biobank2:collection:coll2a,Subcollection1,sub_col1,The first sub collection of collection2,,Amsterdam,NL,,,bbmri-eric:contactID:head_coll2,bbmri-eric:contactID:NL_person1,NL,false,bbmri-eric:ID:NL_biobank2:collection:coll2,bbmri-eric:ID:NL_biobank2,Biobank2,bbmri-eric:networkID:EU_network,bbmri-eric:networkID:EU_network,bbmri-eric:akiID:NL_00002,"CROSS_SECTIONAL,POPULATION_BASED","ANTIBODIES,PHYSIOLOGICAL_BIOCHEMICAL_MEASUREMENTS",2,589,covid19,,133,2,MALE,"ORPHA:1388,ORPHA:139552,urn:miriam:icd:A00.9",5,67,YEAR,"NASAL_SWAB,PLASMA","temperature2to10,temperature-18to-35",T-15710,BMD,1.2.840.10008.5.1.4.1.1.11.1,true,true,DUO_0000024,true,data,data,,,"sample_storage_sop,data_processing_sop,data_transport_sop,data_storage_sop",accredited
id,name,acronym,description,url,location,country,latitude,longitude,head,contact,national_node,withdrawn,parent_collection,biobank,biobank_label,network,combined_network,also_known,type,data_categories,order_of_magnitude,size,categories,timestamp,number_of_donors,order_of_magnitude_donors,sex,diagnosis_available,age_low,age_high,age_unit,materials,storage_temperatures,body_part_examined,imaging_modality,image_dataset_type,collaboration_commercial,collaboration_non_for_profit,data_use,commercial_use,access_fee,access_joint_project,access_description,access_uri,sop,combined_quality,studies
bbmri-eric:ID:DE_biobank1:collection:coll1,Collection1 of biobank1,coll1,The first collection of biobank1,,Berlin,DE,,,bbmri-eric:contactID:DE_collection1,bbmri-eric:contactID:DE_collection1,DE,false,,bbmri-eric:ID:DE_biobank1,Biobank1,bbmri-eric:networkID:DE_nw_coll1,"bbmri-eric:networkID:DE_network1,bbmri-eric:networkID:DE_nw_coll1",bbmri-eric:akiID:DE_00001,"COHORT,RD","MEDICAL_RECORDS,BLOOD",3,1333,"rare_disease,oncology",,600,2,"MALE,NEUTERED_MALE","ORPHA:100001,ORPHA:101009",2,88,YEAR,"DNA,PERIPHERAL_BLOOD_CELLS",temperature2to10,T-04000,BDUS,1.2.840.10008.5.1.4.1.1.104.2,true,true,DUO_0000003,true,"samples,data,images","data,images",,,"sample_transport_sop,data_processing_sop",accredited,bbmri-eric:studyID:DE_112233
bbmri-eric:ID:NL_biobank2:collection:coll2,The second collection of biobank2,coll2_bb2,This is the description of the second collection of biobank2,,Amsterdam,NL,,,bbmri-eric:contactID:head_coll2,bbmri-eric:contactID:NL_person1,NL,false,,bbmri-eric:ID:NL_biobank2,Biobank2,bbmri-eric:networkID:EU_network,bbmri-eric:networkID:EU_network,bbmri-eric:akiID:NL_00001,"CASE_CONTROL,QUALITY_CONTROL","BIOLOGICAL_SAMPLES,IMAGING_DATA,NATIONAL_REGISTRIES",4,11000,"covid19,paediatrics",,9000,3,FEMALE,"urn:miriam:icd:W00,ORPHA:3000",0,90,YEAR,"FECES,PATHOGEN,URINE",temperatureLN,T-58200,CR,1.2.840.10008.5.1.4.1.1.130,true,true,DUO_0000042,false,"samples,data,images","samples,data,images",Access is possible need to pay a fee,,"sample_processing_sop,sample_transport_sop,data_transport_sop,data_storage_sop",accredited,
bbmri-eric:ID:NL_biobank2:collection:coll2a,Subcollection1,sub_col1,The first sub collection of collection2,,Amsterdam,NL,,,bbmri-eric:contactID:head_coll2,bbmri-eric:contactID:NL_person1,NL,false,bbmri-eric:ID:NL_biobank2:collection:coll2,bbmri-eric:ID:NL_biobank2,Biobank2,bbmri-eric:networkID:EU_network,bbmri-eric:networkID:EU_network,bbmri-eric:akiID:NL_00002,"CROSS_SECTIONAL,POPULATION_BASED","ANTIBODIES,PHYSIOLOGICAL_BIOCHEMICAL_MEASUREMENTS",2,589,covid19,,133,2,MALE,"ORPHA:1388,ORPHA:139552,urn:miriam:icd:A00.9",5,67,YEAR,"NASAL_SWAB,PLASMA","temperature2to10,temperature-18to-35",T-15710,BMD,1.2.840.10008.5.1.4.1.1.11.1,true,true,DUO_0000024,true,data,data,,,"sample_storage_sop,data_processing_sop,data_transport_sop,data_storage_sop",accredited,
3 changes: 2 additions & 1 deletion data/biobank-directory/demo/NationalNodes.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
id,description,dns,contact_persons,data_refresh,date_start,date_end
DE,Germany,,,manual,,
EU,European Union,,,manual,,
NL,Netherlands,https://external_server.nl,,external_server,,
NL,Netherlands,https://external_server.nl,,external_server,,
EXT,External Countries,,,manual,,
2 changes: 2 additions & 0 deletions data/biobank-directory/demo/Studies.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,title,description,type,sex,age_high,age_low,age_unit,also_known,national_node
bbmri-eric:studyID:DE_112233,Dummy study,This is just a fake study with demo data,Interventional,"FEMALE,MALE",10,50,YEAR,bbmri-eric:akiID:DE_00002,EXT
Loading

0 comments on commit a5e8310

Please sign in to comment.