Skip to content

Commit 57fdc5f

Browse files
Merge pull request #101 from scottwittenburg/display-import-errors-on-client
Provide more details to client when encountering errors on import
2 parents c87e43e + 0da82ee commit 57fdc5f

File tree

2 files changed

+148
-112
lines changed

2 files changed

+148
-112
lines changed

client/src/components/DataImportExport.vue

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ export default {
99
importEnabled: false,
1010
exportEnabled: false,
1111
importing: false,
12-
importDialog: false
12+
importDialog: false,
13+
importErrorText: "",
14+
importErrors: false
1315
}),
1416
async created() {
1517
var { data: result } = await this.girderRest.get(
@@ -22,19 +24,26 @@ export default {
2224
...mapActions(["loadSessions"]),
2325
async importData() {
2426
this.importing = true;
27+
this.importErrorText = "";
28+
this.importErrors = false;
2529
try {
2630
var { data: result } = await this.girderRest.post("miqa/data/import");
2731
this.importing = false;
28-
this.$snackbar({
29-
text: `Import finished.
30-
With ${result.success} scans succeeded and ${result.failed} failed.`,
31-
timeout: 6000
32-
});
32+
if (result.errorMsg) {
33+
this.importErrorText = `${result.success} scans succeeded, ${result.failed} failed.\n\n${result.errorMsg}`;
34+
this.importErrors = true;
35+
} else {
36+
this.$snackbar({
37+
text: `Import finished.
38+
With ${result.success} scans succeeded and ${result.failed} failed.`,
39+
timeout: 6000
40+
});
41+
}
3342
this.loadSessions();
3443
} catch (ex) {
3544
this.importing = false;
3645
this.$snackbar({
37-
text: "Import failed. Refer console for detail."
46+
text: "Import failed. Refer to server logs for details."
3847
});
3948
console.error(ex.response);
4049
}
@@ -84,7 +93,31 @@ export default {
8493
</v-card-actions>
8594
</v-card>
8695
</v-dialog>
96+
<v-dialog v-model="importErrors" content-class="import-error-dialog">
97+
<v-card>
98+
<v-card-title class="title">Import Errors Encountered</v-card-title>
99+
<v-card-text class="console-format">
100+
{{ importErrorText }}
101+
</v-card-text>
102+
<v-card-actions>
103+
<v-spacer></v-spacer>
104+
<v-btn color="primary" text @click="importErrors = false">
105+
Ok
106+
</v-btn>
107+
</v-card-actions>
108+
</v-card>
109+
</v-dialog>
87110
</div>
88111
</template>
89112

90-
<style lang="scss" scoped></style>
113+
<style lang="scss">
114+
.import-error-dialog {
115+
position: relative;
116+
width: 100%;
117+
margin: 48px;
118+
}
119+
.console-format {
120+
white-space: pre-wrap;
121+
font-family: monospace;
122+
}
123+
</style>

server/miqa_server/session.py

Lines changed: 107 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from jsonschema import validate
55
from jsonschema.exceptions import ValidationError as JSONValidationError
66
import os
7+
import sys
8+
import traceback
79

810
from girder.api.rest import Resource, setResponseHeader, setContentDisposition
911
from girder.api import access, rest
@@ -135,125 +137,126 @@ def _getSessions(self):
135137
Description('')
136138
.errorResponse())
137139
def dataImport(self, params):
138-
user = self.getCurrentUser()
139-
importpath = os.path.expanduser(Setting().get(importpathKey))
140-
if not os.path.isfile(importpath):
141-
raise RestException('import path does not exist ({0}'.format(importpath), code=404)
140+
successCount = 0
141+
failedCount = 0
142+
errorMsg = ''
143+
try:
144+
user = self.getCurrentUser()
145+
importpath = os.path.expanduser(Setting().get(importpathKey))
142146

143-
json_content = None
147+
json_content = None
144148

145-
if importpath.endswith('.csv'):
146-
with open(importpath) as fd:
147-
csv_content = fd.read()
148-
try:
149+
if importpath.endswith('.csv'):
150+
with open(importpath) as fd:
151+
csv_content = fd.read()
149152
json_content = csvContentToJsonObject(csv_content)
150153
validate(json_content, schema)
151-
except (JSONValidationError, Exception) as inst:
152-
return {
153-
"error": 'Invalid CSV file: {0}'.format(inst.message),
154-
"success": successCount,
155-
"failed": failedCount
156-
}
157-
else:
158-
with open(importpath) as json_file:
159-
json_content = json.load(json_file)
160-
try:
154+
else:
155+
with open(importpath) as json_file:
156+
json_content = json.load(json_file)
161157
validate(json_content, schema)
162-
except JSONValidationError as inst:
163-
return {
164-
"error": 'Invalid JSON file: {0}'.format(inst.message),
165-
"success": successCount,
166-
"failed": failedCount
167-
}
168158

169-
existingSessionsFolder = self.findSessionsFolder(user)
170-
if existingSessionsFolder:
171-
existingSessionsFolder['name'] = 'sessions_' + \
172-
datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
173-
Folder().save(existingSessionsFolder)
174-
sessionsFolder = self.findSessionsFolder(user, True)
175-
Item().createItem('json', user, sessionsFolder, description=json.dumps(json_content))
159+
existingSessionsFolder = self.findSessionsFolder(user)
160+
if existingSessionsFolder:
161+
existingSessionsFolder['name'] = 'sessions_' + \
162+
datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
163+
Folder().save(existingSessionsFolder)
164+
sessionsFolder = self.findSessionsFolder(user, True)
165+
Item().createItem('json', user, sessionsFolder, description=json.dumps(json_content))
176166

177-
datasetRoot = json_content['data_root']
178-
experiments = json_content['experiments']
179-
sites = json_content['sites']
167+
datasetRoot = json_content['data_root']
168+
experiments = json_content['experiments']
169+
sites = json_content['sites']
180170

181-
successCount = 0
182-
failedCount = 0
183-
sites = set()
184-
for scan in json_content['scans']:
185-
experimentId = scan['experiment_id']
186-
experimentNote = ''
187-
for experiment in experiments:
188-
if experiment['id'] == experimentId:
189-
experimentNote = experiment['note']
190-
scanPath = scan['path']
191-
site = scan['site_id']
192-
sites.add(site)
193-
scanId = scan['id']
194-
scanType = scan['type']
195-
scanName = scanId+'_'+scanType
196-
niftiFolder = os.path.expanduser(os.path.join(datasetRoot, scanPath))
197-
if not os.path.isdir(niftiFolder):
198-
failedCount += 1
199-
continue
200-
experimentFolder = Folder().createFolder(
201-
sessionsFolder, experimentId, parentType='folder', reuseExisting=True)
202-
scanFolder = Folder().createFolder(
203-
experimentFolder, scanName, parentType='folder', reuseExisting=True)
204-
meta = {
205-
'experimentId': experimentId,
206-
'experimentNote': experimentNote,
207-
'site': site,
208-
'scanId': scanId,
209-
'scanType': scanType
210-
}
211-
if 'decision' in scan:
212-
meta['rating'] = convertDecisionToRating(scan['decision'])
213-
if 'note' in scan:
214-
meta['note'] = scan['note']
215-
Folder().setMetadata(scanFolder, meta)
216-
currentAssetstore = Assetstore().getCurrent()
217-
if 'images' in scan:
218-
scanImages = scan['images']
219-
# Import images one at a time because the user provided a list
220-
for scanImage in scanImages:
221-
absImagePath = os.path.join(niftiFolder, scanImage)
171+
172+
sites = set()
173+
for scan in json_content['scans']:
174+
experimentId = scan['experiment_id']
175+
experimentNote = ''
176+
for experiment in experiments:
177+
if experiment['id'] == experimentId:
178+
experimentNote = experiment['note']
179+
scanPath = scan['path']
180+
site = scan['site_id']
181+
sites.add(site)
182+
scanId = scan['id']
183+
scanType = scan['type']
184+
scanName = scanId+'_'+scanType
185+
niftiFolder = os.path.expanduser(os.path.join(datasetRoot, scanPath))
186+
if not os.path.isdir(niftiFolder):
187+
errorMsg += '{0}: path {1} does not exist\n'.format(experimentId, niftiFolder)
188+
failedCount += 1
189+
continue
190+
experimentFolder = Folder().createFolder(
191+
sessionsFolder, experimentId, parentType='folder', reuseExisting=True)
192+
scanFolder = Folder().createFolder(
193+
experimentFolder, scanName, parentType='folder', reuseExisting=True)
194+
meta = {
195+
'experimentId': experimentId,
196+
'experimentNote': experimentNote,
197+
'site': site,
198+
'scanId': scanId,
199+
'scanType': scanType
200+
}
201+
if 'decision' in scan:
202+
meta['rating'] = convertDecisionToRating(scan['decision'])
203+
if 'note' in scan:
204+
meta['note'] = scan['note']
205+
Folder().setMetadata(scanFolder, meta)
206+
currentAssetstore = Assetstore().getCurrent()
207+
if 'images' in scan:
208+
scanImages = scan['images']
209+
# Import images one at a time because the user provided a list
210+
for scanImage in scanImages:
211+
absImagePath = os.path.join(niftiFolder, scanImage)
212+
Assetstore().importData(
213+
currentAssetstore, parent=scanFolder, parentType='folder', params={
214+
'fileIncludeRegex': '^{0}$'.format(scanImage),
215+
'importPath': niftiFolder,
216+
}, progress=noProgress, user=user, leafFoldersAsItems=False)
217+
imageOrderDescription = {
218+
'orderDescription': {
219+
'images': scanImages
220+
}
221+
}
222+
else:
223+
scanImagePattern = scan['imagePattern']
224+
# Import all images in directory at once because user provide a file pattern
222225
Assetstore().importData(
223226
currentAssetstore, parent=scanFolder, parentType='folder', params={
224-
'fileIncludeRegex': '^{0}$'.format(scanImage),
227+
'fileIncludeRegex': scanImagePattern,
225228
'importPath': niftiFolder,
226229
}, progress=noProgress, user=user, leafFoldersAsItems=False)
227-
imageOrderDescription = {
228-
'orderDescription': {
229-
'images': scanImages
230-
}
231-
}
232-
else:
233-
scanImagePattern = scan['imagePattern']
234-
# Import all images in directory at once because user provide a file pattern
235-
Assetstore().importData(
236-
currentAssetstore, parent=scanFolder, parentType='folder', params={
237-
'fileIncludeRegex': scanImagePattern,
238-
'importPath': niftiFolder,
239-
}, progress=noProgress, user=user, leafFoldersAsItems=False)
240-
imageOrderDescription = {
241-
'orderDescription': {
242-
'imagePattern': scanImagePattern
230+
imageOrderDescription = {
231+
'orderDescription': {
232+
'imagePattern': scanImagePattern
233+
}
243234
}
244-
}
245-
Item().createItem(name='imageOrderDescription',
246-
creator=user,
247-
folder=scanFolder,
248-
reuseExisting=True,
249-
description=json.dumps(imageOrderDescription))
250-
successCount += 1
251-
tryAddSites(sites, self.getCurrentUser())
252-
return {
253-
"success": successCount,
254-
"failed": failedCount
235+
Item().createItem(name='imageOrderDescription',
236+
creator=user,
237+
folder=scanFolder,
238+
reuseExisting=True,
239+
description=json.dumps(imageOrderDescription))
240+
successCount += 1
241+
tryAddSites(sites, self.getCurrentUser())
242+
except Exception as inst:
243+
errorMsg += '{0}\n\n'.format(inst)
244+
245+
stringBuffer = io.StringIO()
246+
excType, excVal, excTb = sys.exc_info()
247+
traceback.print_exception(excType, excVal, excTb, file=stringBuffer)
248+
errorMsg += stringBuffer.getvalue()
249+
250+
result = {
251+
'success': successCount,
252+
'failed': failedCount
255253
}
256254

255+
if errorMsg != '':
256+
result['errorMsg'] = errorMsg
257+
258+
return result
259+
257260
@access.user
258261
@autoDescribeRoute(
259262
Description('')

0 commit comments

Comments
 (0)