Skip to content

Commit fca458e

Browse files
authored
Export dive metadata (#259)
* incrmeent vue-girder-slicer-cli-ui version * yarn.lock update * updating imports * increment slicer verison * increment verions * exporting DIVE Metadata Format * post request params update * updating version numbers
1 parent 5057374 commit fca458e

7 files changed

Lines changed: 188 additions & 10 deletions

File tree

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dive-dsa",
3-
"version": "1.11.1",
3+
"version": "1.11.2",
44
"author": {
55
"name": "Kitware, Inc.",
66
"email": "Bryon.Lewis@kitware.com"

client/platform/web-girder/api/divemetadata.service.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,26 @@ async function runSlicerMetadataTask(rootId: string, taskId: string, filters: DI
182182
return girderRest.post<JobResponse>(`dive_metadata/${rootId}/slicer-cli-task`, { taskId, filterParams: { filters, params } }, { params: { taskId, filterParams: { filters, params } } });
183183
}
184184

185+
async function exportDiveMetadata(folderId: string, filters: DIVEMetadataFilter, format: 'csv' | 'json') {
186+
const response = await girderRest.post(`dive_metadata/${folderId}/export`, null, {
187+
params: { format, filters },
188+
responseType: 'blob',
189+
});
190+
191+
const blob = new Blob([response.data], {
192+
type: format === 'csv' ? 'text/csv' : 'application/json',
193+
});
194+
195+
const url = URL.createObjectURL(blob);
196+
const link = document.createElement('a');
197+
link.href = url;
198+
link.download = `metadata_export.${format}`;
199+
document.body.appendChild(link);
200+
link.click();
201+
document.body.removeChild(link);
202+
URL.revokeObjectURL(url);
203+
}
204+
185205
export {
186206
getMetadataFilterValues,
187207
filterDiveMetadata,
@@ -195,4 +215,5 @@ export {
195215
updateDiveMetadataDisplay,
196216
updateDiveMetadataSlicerConfig,
197217
runSlicerMetadataTask,
218+
exportDiveMetadata,
198219
};
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<script lang="ts">
2+
import { DIVEMetadataFilter, exportDiveMetadata } from 'platform/web-girder/api/divemetadata.service';
3+
import {
4+
defineComponent,
5+
PropType,
6+
ref,
7+
Ref,
8+
} from 'vue';
9+
10+
export default defineComponent({
11+
name: 'DIVEMetadataExport',
12+
components: {
13+
},
14+
props: {
15+
metadataRoot: {
16+
type: String,
17+
required: true,
18+
},
19+
filters: {
20+
type: Object as PropType<DIVEMetadataFilter>,
21+
default: {},
22+
},
23+
},
24+
setup(props) {
25+
const processing = ref(false);
26+
const format: Ref<'json' | 'csv'> = ref('json');
27+
const exportMetadata = async () => {
28+
processing.value = true;
29+
await exportDiveMetadata(props.metadataRoot, props.filters, format.value);
30+
processing.value = false;
31+
};
32+
return {
33+
processing,
34+
format,
35+
exportMetadata,
36+
};
37+
},
38+
});
39+
</script>
40+
41+
<template>
42+
<div>
43+
<v-menu
44+
offset-y
45+
offset-x
46+
nudge-left="90"
47+
open-on-hover
48+
open-delay="250"
49+
close-delay="500"
50+
>
51+
<template #activator="{ on, attrs }">
52+
<v-btn
53+
v-bind="attrs"
54+
v-on="on"
55+
56+
@click="exportMetadata"
57+
>
58+
<v-icon>mdi-export</v-icon>
59+
<span style="font-size: 0.75em">
60+
{{ format }}
61+
</span>
62+
</v-btn>
63+
</template>
64+
<v-card outlined>
65+
<v-card-title>Export Format</v-card-title>
66+
<v-list dense>
67+
<v-list-item
68+
@click="format = 'json'"
69+
>
70+
<v-list-item-content>
71+
<v-list-item-title>JSON</v-list-item-title>
72+
</v-list-item-content>
73+
</v-list-item>
74+
<v-list-item
75+
@click="format = 'csv'"
76+
>
77+
<v-list-item-content>
78+
<v-list-item-title>CSV</v-list-item-title>
79+
</v-list-item-content>
80+
</v-list-item>
81+
</v-list>
82+
</v-card>
83+
</v-menu>
84+
</div>
85+
</template>
86+
87+
<style scoped>
88+
</style>

client/platform/web-girder/views/DIVEMetadataFilter.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
1010
import DIVEMetadataFilterItemVue from './DIVEMetadataFilterItem.vue';
1111
import DIVEMetadataCloneVue from './DIVEMetadataClone.vue';
1212
import DIVEMetadataSlicerVue from './DIVEMetadataSlicer.vue';
13+
import DIVEMetadataExportVue from './DIVEMetadataExport.vue';
1314
1415
export default defineComponent({
1516
name: 'DIVEMetadataFilter',
16-
components: { DIVEMetadataFilterItemVue, DIVEMetadataCloneVue, DIVEMetadataSlicerVue },
17+
components: {
18+
DIVEMetadataFilterItemVue, DIVEMetadataCloneVue, DIVEMetadataSlicerVue, DIVEMetadataExportVue,
19+
},
1720
props: {
1821
currentPage: {
1922
type: Number,
@@ -328,7 +331,8 @@ export default defineComponent({
328331
</v-btn>
329332

330333
<v-spacer />
331-
<DIVEMetadataSlicerVue v-if="showSlicerCLI" :filters="currentFilter" :metadata-root="id" class="pr-8" @job-complete="jobCompleted()" />
334+
<DIVEMetadataSlicerVue v-if="showSlicerCLI" :filters="currentFilter" :metadata-root="id" class="pr-2" @job-complete="jobCompleted()" />
335+
<DIVEMetadataExportVue :metadata-root="id" :filters="currentFilter" class="pr-4" />
332336
<v-chip><span class="pr-1">Filtered:</span>{{ filtered }} / {{ count }}</v-chip>
333337
<v-select
334338
v-model="sortValue"

client/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11003,10 +11003,10 @@ vue-eslint-parser@^9.0.1, vue-eslint-parser@^9.0.3, vue-eslint-parser@^9.3.1, vu
1100311003
lodash "^4.17.21"
1100411004
semver "^7.3.6"
1100511005

11006-
vue-girder-slicer-cli-ui@^0.1.2:
11007-
version "0.1.2"
11008-
resolved "https://registry.yarnpkg.com/vue-girder-slicer-cli-ui/-/vue-girder-slicer-cli-ui-0.1.2.tgz#d841a414441c6227876d4f693c861119e43ca4d4"
11009-
integrity sha512-wsT6NtYaYthJYtIRxSPNxkvaOGcorr8+d2dFVCMtLkbH7s2eiwCdiJWeTNG/dBrLVBoUAM7WjmX/pO9/UIcQww==
11006+
vue-girder-slicer-cli-ui@^0.1.3:
11007+
version "0.1.3"
11008+
resolved "https://registry.yarnpkg.com/vue-girder-slicer-cli-ui/-/vue-girder-slicer-cli-ui-0.1.3.tgz#1b09edddea9926e7b54062f1c46dc8e13d41bf8c"
11009+
integrity sha512-/iePzyDgFF2tNZfzjKdaO++lYjtgEMfOJSupwP5HuX/l52iFA2npref7SK1V7x9sbBjUYP2/vna2YqxQc/nwQw==
1101011010
dependencies:
1101111011
"@jamescoyle/vue-icon" "^0.1.2"
1101211012
"@mdi/js" "^7.2.96"

server/dive_server/views_metadata.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import csv
2+
import io
13
import json
24
import math
35
import re
46

57
import cherrypy
68
from girder.api import access
79
from girder.api.describe import Description, autoDescribeRoute
8-
from girder.api.rest import Resource, getApiUrl
10+
from girder.api.rest import Resource, getApiUrl, setRawResponse, setResponseHeader
911
from girder.constants import AccessType
1012
from girder.exceptions import RestException
1113
from girder.models.file import File
@@ -18,7 +20,7 @@
1820
from girder_worker.girder_plugin.utils import getWorkerApiUrl
1921
import pymongo
2022

21-
from dive_utils import FALSY_META_VALUES, TRUTHY_META_VALUES
23+
from dive_utils import FALSY_META_VALUES, TRUTHY_META_VALUES, setContentDisposition
2224
from dive_utils.constants import (
2325
DIVEMetadataClonedFilter,
2426
DIVEMetadataClonedFilterBase,
@@ -147,6 +149,7 @@ def __init__(self, resourceName):
147149
),
148150
self.run_slicer_cli_task,
149151
)
152+
self.route("POST", (":id", "export"), self.export_metadata)
150153

151154
@access.user
152155
@autoDescribeRoute(
@@ -1105,3 +1108,65 @@ def run_slicer_cli_task(
11051108
job = Job().save(job)
11061109
Job().scheduleJob(job)
11071110
return job
1111+
1112+
@access.user
1113+
@autoDescribeRoute(
1114+
Description("Export filtered DIVE metadata as JSON or CSV")
1115+
.modelParam(
1116+
"id",
1117+
description="Base root Folder to filter on",
1118+
model=Folder,
1119+
level=AccessType.READ,
1120+
)
1121+
.jsonParam(
1122+
"filters",
1123+
"JSON Settings for the filtering",
1124+
required=False,
1125+
)
1126+
.param(
1127+
"format",
1128+
description="Export format: 'json' or 'csv'",
1129+
required=True,
1130+
enum=["json", "csv"],
1131+
)
1132+
)
1133+
def export_metadata(self, folder, filters, format):
1134+
if folder['meta'].get(DIVEMetadataMarker, False) is False:
1135+
raise RestException('Folder is not a DIVE Metadata folder', code=404)
1136+
1137+
user = self.getCurrentUser()
1138+
query = self.get_filter_query(folder, user, filters)
1139+
metadata_items = list(DIVE_Metadata().find(query, user=user))
1140+
1141+
filename = f"metadata_export.{format}"
1142+
if not metadata_items:
1143+
raise RestException('No metadata items to export.')
1144+
1145+
if format == 'csv':
1146+
output = io.StringIO(newline='')
1147+
# Infer CSV headers from all keys used across items
1148+
headers = sorted(
1149+
{key for item in metadata_items for key in item.get('metadata', {}).keys()}
1150+
)
1151+
1152+
writer = csv.DictWriter(output, fieldnames=headers, extrasaction='ignore')
1153+
writer.writeheader()
1154+
for item in metadata_items:
1155+
row = {key: item.get('metadata', {}).get(key, '') for key in headers}
1156+
writer.writerow(row)
1157+
1158+
csv_output = output.getvalue()
1159+
output.close()
1160+
1161+
setRawResponse()
1162+
setContentDisposition(filename, mime='text/csv')
1163+
setResponseHeader('Content-Type', 'text/csv')
1164+
return csv_output.encode('utf-8')
1165+
1166+
else: # JSON
1167+
export_data = [item['metadata'] for item in metadata_items]
1168+
setContentDisposition(filename, mime='application/json')
1169+
setRawResponse()
1170+
1171+
setResponseHeader('Content-Type', 'application/json')
1172+
return json.dumps(export_data).encode('utf-8')

server/dive_server/web_client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
]
1515
},
1616
"dependencies": {
17-
"dive-dsa": "^1.10.25",
17+
"dive-dsa": "^1.11.1",
1818
"webpack": "^2.7.0",
1919
"copy-webpack-plugin": "^4.5.2"
2020
}

0 commit comments

Comments
 (0)