Skip to content

Commit

Permalink
Merge pull request #25 from DigitalSlideArchive/add-dicomweb-support
Browse files Browse the repository at this point in the history
Add initial DICOMweb assetstore support
  • Loading branch information
willdunklin authored Nov 8, 2023
2 parents 102f727 + 3330e96 commit ba13e01
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 26 deletions.
95 changes: 94 additions & 1 deletion import_tracker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import time

from girder import plugin
from girder import logger, plugin
from girder.api.describe import autoDescribeRoute
from girder.api.rest import boundHandler
from girder.constants import AccessType
Expand Down Expand Up @@ -162,12 +162,100 @@ def shouldImportFileWrapper(self, path, params):
AbstractAssetstoreAdapter.shouldImportFile = shouldImportFileWrapper


def wrapDICOMImport(assetstoreResource):
baseImportData = assetstoreResource.importData
baseImportData.description.param(
'excludeExisting',
'If true, then a file with an import path that is already in the '
'system is not imported, even if it is not in the destination '
'hierarchy.', dataType='boolean', required=False, default=False)

@boundHandler(ctx=assetstoreResource)
@autoDescribeRoute(baseImportData.description)
def dwaImportDataWrapper(self, assetstore, destinationId, destinationType, filters,
progress, excludeExisting):

user = self.getCurrentUser()
params = {
'destinationId': destinationId,
'destinationType': destinationType,
'filters': filters,
'progress': str(progress).lower(),
}
if excludeExisting:
params['excludeExisting'] = str(excludeExisting).lower()

importRecord = AssetstoreImport().createAssetstoreImport(assetstore, params)
job = Job().createJob(
title=f'Import from {assetstore["name"]}',
type='assetstore_import',
public=False,
user=user,
kwargs=params,
)
job = Job().updateJob(job, '%s - Starting import from %s\n' % (
time.strftime('%Y-%m-%d %H:%M:%S'),
assetstore['name']
), status=JobStatus.RUNNING)

try:
with ProgressContext(progress, user=user, title='Importing data') as ctx:
try:
jobRec = {
'id': str(job['_id']),
'count': 0,
'skip': 0,
'lastlog': time.time(),
'logcount': 0,
}
self._importData(
assetstore,
params={
**params,
'_job': jobRec},
progress=ctx)

success = True
Job().updateJob(job, '%s - Finished. Checked %d, skipped %d\n' % (
time.strftime('%Y-%m-%d %H:%M:%S'),
jobRec['count'], jobRec['skip'],
), status=JobStatus.SUCCESS)

except ImportTrackerCancelError:
Job().updateJob(job, '%s - Canceled' % (
time.strftime('%Y-%m-%d %H:%M:%S'),
))
success = 'canceled'

except Exception as exc:
Job().updateJob(job, '%s - Failed with %s\n' % (
time.strftime('%Y-%m-%d %H:%M:%S'),
exc,
), status=JobStatus.ERROR)
success = False

importRecord = AssetstoreImport().markEnded(importRecord, success)
return importRecord

for key in {'accessLevel', 'description', 'requiredScopes'}:
setattr(dwaImportDataWrapper, key, getattr(baseImportData, key))

assetstoreResource.importData = dwaImportDataWrapper
assetstoreResource.removeRoute('POST', (':id', 'import'))
assetstoreResource.route('POST', (':id', 'import'), assetstoreResource.importData)


class GirderPlugin(plugin.GirderPlugin):
DISPLAY_NAME = 'import_tracker'
CLIENT_SOURCE_PATH = 'web_client'

def load(self, info):
plugin.getPlugin('jobs').load(info)
try:
import large_image_source_dicom # noqa
plugin.getPlugin('dicomweb').load(info)
except ImportError:
pass
ModelImporter.registerModel(
'assetstoreImport', AssetstoreImport, 'import_tracker'
)
Expand All @@ -178,3 +266,8 @@ def load(self, info):
wrapImportData(info['apiRoot'].assetstore)

info['apiRoot'].folder.route('PUT', (':id', 'move'), moveFolder)

if hasattr(info['apiRoot'], 'dicomweb_assetstore'):
wrapDICOMImport(info['apiRoot'].dicomweb_assetstore)
else:
logger.info('dicomweb_assetstore not found')
29 changes: 19 additions & 10 deletions import_tracker/web_client/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import $ from 'jquery';

import AssetstoreView from '@girder/core/views/body/AssetstoresView';
import FilesystemImportView from '@girder/core/views/body/FilesystemImportView';
import S3ImportView from '@girder/core/views/body/S3ImportView';
Expand All @@ -22,16 +24,23 @@ import './JobStatus';
wrap(AssetstoreView, 'render', function (render) {
// Call the underlying render function that we are wrapping
render.call(this);

this.$el.find('.g-current-assetstores-container .g-body-title').after(
'<a class="g-view-imports btn btn-sm btn-primary" href="#assetstore/all_imports"><i class="icon-link-ext"></i>View all past Imports</a>'
);

// Inject new button into each assetstore
const assetstores = this.collection.toArray();
this.$('.g-assetstore-import-button-container').after(
(i) => importDataButton({ importsPageLink: `#assetstore/${assetstores[i].id}/imports` })
);
// defer adding buttons so optional plugins can render first.
window.setTimeout(() => {
this.$el.find('.g-current-assetstores-container .g-body-title').after(
'<a class="g-view-imports btn btn-sm btn-primary" href="#assetstore/all_imports"><i class="icon-link-ext"></i>View all past Imports</a>'
);

// Inject new button into each assetstore
const assetstores = this.collection;
this.$('.g-assetstore-import-button-container').after(
function () {
// we can't just use the index of the after call, since not
// all assetstores will have import buttons.
const assetstore = assetstores.get($(this).closest('.g-assetstore-buttons').find('[cid]').attr('cid'));
return importDataButton({ importsPageLink: `#assetstore/${assetstore.id}/imports` });
}
);
}, 0);
});

// Add duplicate_files option to Import Asset form
Expand Down
24 changes: 13 additions & 11 deletions import_tracker/web_client/templates/importList.pug
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ table.g-imports-list-table
-
var anyRegex = false;
var anyLeafed = false;
var anyNoProgress;
var anyImportPath = false;
var anyNoProgress = false;
var otherParams = [];
var showCount = false;
for _import in imports
Expand All @@ -31,9 +32,11 @@ table.g-imports-list-table
- anyLeafed = true
if !_import.params.progress
- anyNoProgress = true
- Object.keys(_import.params).forEach((key) => { if (['importPath', 'destinationId', 'destinationType', 'leafFoldersAsItems', 'progress', 'fileIncludeRegex', 'fileExcludeRegex'].indexOf(key) < 0 && otherParams.indexOf(key) < 0) { otherParams.push(key); } });
- Object.keys(_import.params).forEach((key) => { if (['importPath', 'destinationId', 'leafFoldersAsItems', 'progress', 'fileIncludeRegex', 'fileExcludeRegex'].indexOf(key) < 0 && otherParams.indexOf(key) < 0) { otherParams.push(key); } });
if _import._count
- showCount = true;
- showCount = true
if _import.params.importPath
- anyImportPath = true
thead
tr
th Actions
Expand All @@ -42,7 +45,8 @@ table.g-imports-list-table
th Started
th Ended
th Assetstore Name
th Import Path
if anyImportPath
th Import Path
//- th Destination Type
th Destination Path
if anyLeafed
Expand All @@ -59,11 +63,11 @@ table.g-imports-list-table
tr
td
if assetstoreExists[i]
div(style='display: flex; justify-content: flex-end;')
div(style='display: flex; padding-left: 10px; padding-right: 10px;')
button.re-import-btn.btn.btn-sm.btn-success(index=i, disabled=(_import._destinationPath =='does not exist'))
i.icon-cw
| Re-Import
button.re-import-edit-btn.btn.btn-sm.btn-primary(index=i, style='margin-left: 5px;', data-toggle='tooltip', title='Edit Import Parameters')
button.re-import-edit-btn.btn.btn-sm.btn-primary(index=i, style='margin-left: 1em;', data-toggle='tooltip', title='Edit Import Parameters')
i.icon-pencil
if showCount
td(data-id=_import._count, data-toggle='tooltip', title=_import._count !== 1 ? `Imported ${_import._count} times` : 'Imported once')
Expand All @@ -74,11 +78,9 @@ table.g-imports-list-table
span= _import.ended ? moment(_import.ended).format('YYYY-MM-DD HH:mm:ss.SSS') : ''
td(data-id=_import._assetstoreName, data-toggle='tooltip', title=_import._assetstoreName)
span= _import._assetstoreName
td(data-id=_import.params.importPath, data-toggle='tooltip', title=_import.params.importPath)
span= _import.params.importPath
//-
td
span= _import.params.destinationType
if anyImportPath
td(data-id=_import.params.importPath, data-toggle='tooltip', title=_import.params.importPath)
span= _import.params.importPath
td(data-id=_import.params.destinationId, data-toggle='tooltip', title=_import._destinationPath + '\n' + _import.params.destinationId)
span= _import._destinationPath
if anyLeafed
Expand Down
32 changes: 28 additions & 4 deletions import_tracker/web_client/views/importList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import $ from 'jquery';
import moment from 'moment';

import AssetstoreModel from '@girder/core/models/AssetstoreModel';
import { AssetstoreType } from '@girder/core/constants';
import View from '@girder/core/views/View';
import router from '@girder/core/router';
import { restRequest } from '@girder/core/rest';
Expand All @@ -22,11 +23,20 @@ var importList = View.extend({
const assetstore = new AssetstoreModel({ _id: importEvent.assetstoreId });
const destType = importEvent.params.destinationType;
const destId = importEvent.params.destinationId;

assetstore.off('g:imported').on('g:imported', function () {
router.navigate(destType + '/' + destId, { trigger: true });
}, this).on('g:error', function (resp) {
this.$('.g-validation-failed-message').text(resp.responseJSON.message);
}, this).import(importEvent.params);
}, this);

assetstore.once('g:fetched', () => {
if (assetstore.get('type') === AssetstoreType.DICOMWEB) {
assetstore.dicomwebImport(importEvent.params);
} else {
assetstore.import(importEvent.params);
}
}).fetch();
},
'click .re-import-edit-btn': function (e) {
const index = Number($(e.currentTarget).attr('index'));
Expand All @@ -35,10 +45,24 @@ var importList = View.extend({
return;
}

// Navigate to re-import page
const navigate = (assetstoreId, importId) => {
const assetstore = new AssetstoreModel({ _id: assetstoreId });
assetstore.once('g:fetched', () => {
if (assetstore.get('type') === AssetstoreType.DICOMWEB) {
// Avoid adding previous import data for DICOMweb imports by navigating to blank import
// TODO: Add DICOMweb-specific re-import view
router.navigate(`dicomweb_assetstore/${assetstoreId}/import`, { trigger: true });
} else {
router.navigate(`assetstore/${assetstoreId}/re-import/${importId}`, { trigger: true });
}
}).fetch();
};

const assetstoreId = importEvent.assetstoreId;
const importId = importEvent._id;
const importId = importEvent._id; // Only individual imports have an _id
if (importId) {
router.navigate(`assetstore/${assetstoreId}/re-import/${importId}`, { trigger: true });
navigate(assetstoreId, importId);
return;
}

Expand All @@ -53,7 +77,7 @@ var importList = View.extend({
i.params.destinationType === importEvent.params.destinationType
)[0]._id;

router.navigate(`assetstore/${assetstoreId}/re-import/${importId}`, { trigger: true });
navigate(assetstoreId, importId);
});
}
},
Expand Down

0 comments on commit ba13e01

Please sign in to comment.