Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(directory): add studies associated to collections #3837

Merged
merged 18 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2451e68
feat(data/biobank-directory): updates model to include Study and asso…
svituz May 31, 2024
fc278d5
feat(data/biobank_directory): updates demo data to include Study
svituz May 31, 2024
4f2b222
feat(data/biobank-directory): fixes Study model
svituz May 31, 2024
7bff877
feat(apps/directory): adds viewing of studies related to collections
svituz May 31, 2024
a19b0ab
test(datamodels): updates the test that checks for the number of tabl…
svituz Jun 11, 2024
69f9741
feat: changes description of Study entity to be more generic and chan…
svituz Sep 9, 2024
55edfe1
feat: restores NationalNode attribute for AlsoKnownIn table to be req…
svituz Sep 9, 2024
4a08c4e
feat: adds EXT national node to the demo
svituz Sep 9, 2024
ba9c1ef
feat(directory): implements changes suggested in the review
svituz Sep 9, 2024
3c3b3e9
test(directory): fixes directory's loader test
svituz Sep 10, 2024
a3e53d0
refactor(app/directory): convert the studyStore to typescript
svituz Sep 10, 2024
b21151a
feat(apps/directory): removes watch of route from StudyReport
svituz Sep 10, 2024
d910bd4
refactor: removes console logging and revert const HOST
svituz Sep 19, 2024
1c3301f
refactor: remove study from the information shown on the collection c…
svituz Sep 19, 2024
5de8478
refactor(directory): adds refLabel to Collection and removes all refe…
svituz Sep 19, 2024
18db96f
refactor: moves the study column under the Belongs To header
svituz Sep 20, 2024
afb9f1f
feat: changes study attribute of Collection entity to be ref_array an…
svituz Sep 26, 2024
ab775d9
feat: change study column name to studies
svituz Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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