diff --git a/Dockerfile b/Dockerfile
index 532df627f..d8f859385 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -29,12 +29,12 @@ RUN apt-get update
RUN apt-get install -y wget
# TODO: we need to start using the keyring instead
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv A8D3785C
-RUN wget "http://repo.mysql.com/mysql-apt-config_0.8.30-1_all.deb" -P /tmp
+RUN wget "http://repo.mysql.com/mysql-apt-config_0.8.35-1_all.deb" -P /tmp
# install lsb-release (a dependency of mysql-apt-config), since dpkg doesn't
# do dependency resolution
RUN apt-get install -y lsb-release
-RUN dpkg --install /tmp/mysql-apt-config_0.8.30-1_all.deb
+RUN dpkg --install /tmp/mysql-apt-config_0.8.35-1_all.deb
# fetch the updated package metadata (in particular, mysql-server)
RUN apt-get update
diff --git a/etl/etl.py b/etl/etl.py
index 1476f813e..343a95560 100644
--- a/etl/etl.py
+++ b/etl/etl.py
@@ -59,10 +59,10 @@
COLLECTION_HEADER_CHK = "collection_uuid"
FIELD_MAP = {x: i for i, x in enumerate([
- "collection_id", "collection_uuid", "name", "collections", "image_types", "supporting_data", "subject_count", "doi",
- "source_url", "cancer_type", "species", "location", "analysis_artifacts", "description", "collection_type",
- "program", "access", "date_updated", "tcia_wiki_collection_id", "license_short_name", "active"
- ])}
+ "collection_id", "collection_uuid", "name", "collections", "image_types", "supporting_data", "subject_count", "doi",
+ "source_url", "cancer_type", "species", "location", "analysis_artifacts", "description", "collection_type",
+ "program", "access", "date_updated", "tcia_wiki_collection_id", "license_short_name", "active", "total_size", "total_size_with_ar"
+])}
TOKENIZED_FIELDS = ["PatientID", "SeriesInstanceUID", "StudyInstanceUID"]
@@ -146,8 +146,8 @@
}
SOLR_SINGLE_VAL = {
- "StudyInstanceUID": ["PatientID", "StudyInstanceUID", "crdc_study_uuid"],
- "SeriesInstanceUID": ["PatientID", "StudyInstanceUID", "SeriesInstanceUID", "crdc_study_uuid", "crdc_series_uuid"]
+ "StudyInstanceUID": ["PatientID", "StudyInstanceUID", "crdc_study_uuid","instance_size"],
+ "SeriesInstanceUID": ["PatientID", "StudyInstanceUID", "SeriesInstanceUID", "crdc_study_uuid", "crdc_series_uuid", "instance_size"]
}
ETL_CONFIG = {}
@@ -356,7 +356,7 @@ def load_collections(filename, data_version="8.0"):
exact_collection_fields = [
"collection_id", "collection_uuid", "name", "collections", "image_types", "supporting_data", "subject_count", "doi",
"source_url", "cancer_type", "species", "location", "analysis_artifacts", "description", "collection_type",
- "access", "date_updated", "active"]
+ "access", "date_updated", "active", "total_size", "total_size_with_ar"]
field_map = FIELD_MAP
for line in csv_reader(collection_file):
if COLLECTION_HEADER_CHK in line:
@@ -366,7 +366,6 @@ def load_collections(filename, data_version="8.0"):
for field in line:
field_map[field] = i
i += 1
- print(field_map)
continue
collex = {
'data': { x: line[field_map[x]] for x in exact_collection_fields },
diff --git a/idc/demo_views.py b/idc/demo_views.py
index 659d7966d..890ff0596 100644
--- a/idc/demo_views.py
+++ b/idc/demo_views.py
@@ -144,7 +144,7 @@ def explore_demo_page(request):
is_json = False
try:
- req = request.GET if request.GET else request.POST
+ req = request.GET if request.method == 'GET' else request.POST
is_dicofdic = (req.get('is_dicofdic', "False").lower() == "true")
source = req.get('data_source_type', DataSource.SOLR)
versions = json.loads(req.get('versions', '[]'))
diff --git a/idc/urls.py b/idc/urls.py
index c15d5ab00..8b419509c 100644
--- a/idc/urls.py
+++ b/idc/urls.py
@@ -49,8 +49,8 @@
re_path(r'^explore/filters/', views.parse_explore_filters, name='parse_explore_filters'),
re_path(r'^explore/bq_string/$', get_query_str_response, name='explore_bq_string'),
re_path(r'^explore/manifest/$', views.explorer_manifest, name='get_explore_manifest'),
+ re_path(r'^explore/manifest/series/$', views.explorer_manifest, name='get_series_ids_manifest'),
re_path(r'^tables/', views.populate_tables, name='populate_tables'),
- re_path(r'^studymp/', views.studymp, name='studymp'),
re_path(r'^warning/', views.warn_page, name='warn'),
re_path(r'^about/$', views.about_page, name='about_page'),
re_path(r'^dashboard/', views.dashboard_page, name='dashboard'),
@@ -60,8 +60,11 @@
re_path(r'^cart/$', views.cart_page, name='cart'),
re_path(r'^explore/cart/$', views.cart_page, name='get_explore_cart'),
re_path(r'^cart_data/$', views.cart_data, name='get_cart_data'),
- re_path(r'^series_ids/(?P[A-Za-z0-9\.\-_]+)/$', views.get_series, name='get_series_by_case'),
- re_path(r'^series_ids/(?P[A-Za-z0-9\.\-_]+)/(?P[0-9\.]+)/$', views.get_series, name='get_series'),
+ re_path(r'^series_ids/$', views.get_series, name='get_series_ids_base'),
+ re_path(r'^series_ids_filter/$', views.get_series, name='get_series_ids_filter'),
+ re_path(r'^series_ids/(?P[A-Za-z0-9\.\-_]+)/$', views.get_series, name='get_series_by_collex'),
+ re_path(r'^series_ids/(?P[A-Za-z0-9\.\-_]+)/(?P[A-Za-z0-9\.\-_]+)/$', views.get_series, name='get_series_by_case'),
+ re_path(r'^series_ids/(?P[A-Za-z0-9\.\-_]+)/(?P[A-Za-z0-9\.\-_]+)/(?P[0-9\.]+)/$', views.get_series, name='get_series'),
re_path(r'^collaborators/$', views.collaborators, name='collaborators'),
re_path(r'^collections/', include('idc_collections.urls')),
re_path(r'^citations/', views.get_citations, name='get_citations'),
diff --git a/idc/views.py b/idc/views.py
index 6e96af000..dfba7d712 100644
--- a/idc/views.py
+++ b/idc/views.py
@@ -23,6 +23,7 @@
import datetime
import re
import copy
+from xmlrpc.client import boolean
from django.conf import settings
from django.contrib.auth.decorators import login_required
@@ -38,7 +39,9 @@
from cohorts.models import Cohort, Cohort_Perms
from idc_collections.models import Program, DataSource, Collection, ImagingDataCommonsVersion, Attribute, Attribute_Tooltips, DataSetType, Citation
-from idc_collections.collex_metadata_utils import build_explorer_context, get_collex_metadata, create_file_manifest, get_cart_data_serieslvl, get_cart_data_studylvl, get_table_data_with_cart_data
+from idc_collections.collex_metadata_utils import (build_explorer_context, get_collex_metadata, create_file_manifest,
+ get_cart_data_serieslvl, get_cart_data_studylvl,
+ get_table_data_with_cart_data, cart_manifest)
from allauth.socialaccount.models import SocialAccount
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse, JsonResponse
@@ -47,7 +50,8 @@
from idc.models import User_Data
from solr_helpers import build_solr_query, query_solr_and_format_result
-
+from idc.settings import MAX_FILE_LIST_REQUEST
+from idc_collections.collex_metadata_utils import convert_disk_size
debug = settings.DEBUG
logger = logging.getLogger(__name__)
@@ -226,7 +230,7 @@ def quota_page(request):
def save_ui_hist(request):
status = 200
try:
- req = request.POST or request.GET
+ req = request.GET if request.method == 'GET' else request.POST
hist = req['his']
try:
user_data = User_Data.objects.get(user_id=request.user.id)
@@ -244,86 +248,6 @@ def save_ui_hist(request):
return JsonResponse({}, status=status)
-# returns various metadata mappings for selected projects used in calculating cart selection
-# counts 'on the fly' client side
-def studymp(request):
- response = {}
- status = 200
- sources = ImagingDataCommonsVersion.objects.get(active=True).get_data_sources(
- active=True, source_type=DataSource.SOLR,
- aggregate_level="StudyInstanceUID"
- )
- data_types = [DataSetType.IMAGE_DATA, DataSetType.ANCILLARY_DATA, DataSetType.DERIVED_DATA]
- data_sets = DataSetType.objects.filter(data_type__in=data_types)
- aggregate_level='StudyInstanceUID'
- versions=[]
- versions = ImagingDataCommonsVersion.objects.filter(
- version_number__in=versions
- ).get_data_versions(active=True) if len(versions) else ImagingDataCommonsVersion.objects.filter(
- active=True
- ).get_data_versions(active=True)
- aux_sources = data_sets.get_data_sources().filter(
- source_type=DataSource.SOLR,
- aggregate_level__in=["case_barcode", "sample_barcode", aggregate_level],
- id__in=versions.get_data_sources().filter(source_type=DataSource.SOLR).values_list("id", flat=True)
- ).distinct()
-
- try:
- req = request.GET if request.GET else request.POST
- filters = json.loads(req.get('filters', '{}'))
-
- mxSeries = int(req.get('mxseries'))
- mxStudies= int(req.get('mxstudies'))
- limit = int(req.get('limit', mxStudies))
- offset = int(req.get('offset',0))
-
- casestudymp = dict()
- studymp = dict()
- projstudymp = dict()
-
- idsEx = get_collex_metadata(
- filters, ['collection_id', 'PatientID','StudyInstanceUID', 'SeriesInstanceUID'], record_limit=limit,
- sources=sources, offset=offset, records_only=True, custom_facets={}, aux_sources=aux_sources,
- collapse_on='StudyInstanceUID', counts_only=False, filtered_needed=False,
- raw_format=True, default_facets=False, sort=None
- )
-
- logger.debug("[STATUS] records pulled: {}".format(len(idsEx['docs'])))
-
- for doc in idsEx['docs']:
- proj=doc['collection_id'][0]
- patientid=doc['PatientID']
- studyid= doc['StudyInstanceUID']
- cnt = len(doc['SeriesInstanceUID'])
-
- if not patientid in casestudymp:
- casestudymp[patientid]={}
- if not proj in projstudymp:
- projstudymp[proj] = {}
- studymp[studyid]={}
- studymp[studyid]['cnt'] = cnt
- studymp[studyid]['proj'] = proj
- studymp[studyid]['PatientID'] = patientid
- studymp[studyid]['val'] = []
- projstudymp[proj][studyid] = cnt
- casestudymp[patientid][studyid] = cnt
-
- response["studymp"] = studymp
- response['casestudymp'] = casestudymp
- response['projstudymp'] = projstudymp
-
- except Exception as e:
- logger.error("[ERROR] While attempting to get studymp:")
- logger.exception(e)
- messages.error(
- request,
- "Encountered an error when attempting to get the studymp - please contact the administrator."
- )
- status = 400
-
- return JsonResponse(response, status=status)
-
-
def populate_tables(request):
response = {}
status = 200
@@ -449,7 +373,6 @@ def explore_data_page(request, filter_path=False, path_filters=None):
collapse_on, is_json, uniques=uniques, totals=totals, with_stats=with_stats, disk_size=disk_size
)
- print(context.keys())
if not('totals' in context):
context['totals']={}
if not('PatientID' in context['totals']):
@@ -534,7 +457,7 @@ def explore_data_page(request, filter_path=False, path_filters=None):
def explorer_manifest(request):
try:
- req = request.GET or request.POST
+ req = request.GET if request.method == 'GET' else request.POST
if req.get('manifest-type', 'file-manifest') == 'bq-manifest' :
messages.error(request, "BigQuery export requires a cohort! Please save your filters as a cohort.")
return JsonResponse({'msg': 'BigQuery export requires a cohort.'}, status=400)
@@ -549,7 +472,7 @@ def explorer_manifest(request):
# by the explore_data_page method and forward it on to that view, returning its response.
def parse_explore_filters(request):
try:
- if not request.GET:
+ if not request.GET or request.method != 'GET':
raise Exception("This call only supports GET!")
raw_filters = {x: request.GET.getlist(x) for x in request.GET.keys()}
filters = {}
@@ -618,7 +541,7 @@ def cart_page(request):
request.session.create()
try:
- req = request.GET if request.GET else request.POST
+ req = request.GET if request.method == 'GET' else request.POST
carthist = json.loads(req.get('carthist', '{}'))
mxseries = req.get('mxseries',0)
mxstudies = req.get('mxstudies',0)
@@ -641,10 +564,12 @@ def cart_data(request):
response = {}
field_list = ['collection_id', 'PatientID', 'StudyInstanceUID', 'SeriesInstanceUID', 'aws_bucket', "source_DOI"]
try:
- req = request.GET if request.GET else request.POST
+ req = request.GET if request.method == 'GET' else request.POST
filtergrp_list = json.loads(req.get('filtergrp_list', '{}'))
aggregate_level = req.get('aggregate_level', 'StudyInstanceUID')
results_level = req.get('results_level', 'StudyInstanceUID')
+ dois_only = bool(req.get('dois_only', 'false').lower() == 'true')
+ size_only = bool(req.get('size_only', 'false').lower() == 'true')
partitions = json.loads(req.get('partitions', '{}'))
@@ -653,13 +578,29 @@ def cart_data(request):
length = int(req.get('length', 100))
mxseries = int(req.get('mxseries',1000))
- if ((len(partitions)>0) and (aggregate_level == 'StudyInstanceUID')):
- response = get_cart_data_studylvl(filtergrp_list, partitions, limit, offset, length, mxseries, results_lvl=results_level)
- elif ((len(partitions)>0) and (aggregate_level == 'SeriesInstanceUID')):
- response = get_cart_data_serieslvl(filtergrp_list, partitions, field_list, limit, offset)
- else:
+ doi_or_size_only = (dois_only or size_only)
+
+ if len(partitions) <= 0 or aggregate_level not in ['SeriesInstanceUID', 'StudyInstanceUID']:
response['numFound'] = 0
response['docs'] = []
+ else:
+ if aggregate_level == 'StudyInstanceUID':
+ response = get_cart_data_studylvl(
+ filtergrp_list, partitions, limit, offset, length, mxseries, with_records=(not doi_or_size_only),
+ results_lvl=results_level, dois_only=dois_only, size_only=size_only
+ )
+ elif aggregate_level == 'SeriesInstanceUID':
+ response = get_cart_data_serieslvl(
+ filtergrp_list, partitions, field_list if (not doi_or_size_only) else None, limit, offset,
+ with_records=(not doi_or_size_only), dois_only=dois_only, size_only=size_only
+ )
+ if dois_only:
+ response = {'dois': response['dois']}
+ if size_only:
+ response = {
+ "total_size": response['total_size'],
+ "display_size": convert_disk_size(response['total_size'])
+ }
except Exception as e:
logger.error("[ERROR] While loading cart:")
logger.exception(e)
@@ -668,33 +609,70 @@ def cart_data(request):
return JsonResponse(response, status=status)
-def get_series(request, patient_id, study_uid=None):
+def get_series(request, collection_id=None, patient_id=None, study_uid=None):
+ status = 200
+ response = {"result": []}
try:
- status = 200
- response = { "result": [] }
- source = ImagingDataCommonsVersion.objects.get(active=True).get_data_sources(
- active=True, source_type=DataSource.SOLR,
- aggregate_level="SeriesInstanceUID"
- ).first()
- filters = {
- "PatientID": [patient_id]
- }
- if study_uid:
- filters["StudyInstanceUID"] = [study_uid]
- filter_query = build_solr_query(
- filters,
- with_tags_for_ex=False,
- search_child_records_by=None, solr_default_op='AND'
- )
- result = query_solr_and_format_result(
- {
- "collection": source.name,
- "fields": ["PatientID", "StudyInstanceUID", "Modality", "crdc_series_uuid", "SeriesInstanceUID", "aws_bucket", "instance_size"],
- "query_string": None,
- "fqs": [filter_query['full_query_str']],
- "facets": None, "sort": None, "counts_only": False, "limit": 2000
+ fields = ["collection_id", "PatientID", "StudyInstanceUID", "Modality", "crdc_series_uuid", "SeriesInstanceUID", "aws_bucket", "instance_size"]
+ result = {}
+ if not collection_id:
+ # This is a request for filters and/or a cart
+ body_unicode = request.body.decode('utf-8')
+ body = json.loads(body_unicode)
+ partitions = body.get("partitions", {})
+ filtergrp_list = body.get("filtergrp_list", {})
+ if not(len(partitions) or len(filtergrp_list)):
+ raise Exception("You can only request series IDs based on a filter, collection ID, Patient ID, or study ID!")
+ if len(partitions):
+ result = cart_manifest(filtergrp_list, partitions, 0, fields, MAX_FILE_LIST_REQUEST)
+ else:
+ source = ImagingDataCommonsVersion.objects.get(active=True).get_data_sources(
+ active=True, source_type=DataSource.SOLR,
+ aggregate_level="SeriesInstanceUID"
+ ).first()
+ filter_query = build_solr_query(
+ { w: v for x in filtergrp_list for w, v in list(x.items())},
+ with_tags_for_ex=False,
+ search_child_records_by={w: "StudyInstanceUID" for x in filtergrp_list for w in x}, solr_default_op='AND'
+ )
+ result = query_solr_and_format_result(
+ {
+ "collection": source.name,
+ "fields": fields,
+ "query_string": None,
+ "fqs": [filter_query['full_query_str']],
+ "facets": {'instance_size': 'sum(instance_size)'}, "sort": None, "counts_only": False, "limit": MAX_FILE_LIST_REQUEST,
+ "totals": ['SeriesInstanceUID', 'collection_id', 'PatientID', 'StudyInstanceUID']
+ },
+ normalize_facets=False
+ )
+ else:
+ source = ImagingDataCommonsVersion.objects.get(active=True).get_data_sources(
+ active=True, source_type=DataSource.SOLR,
+ aggregate_level="SeriesInstanceUID"
+ ).first()
+ filters = {
+ "collection_id": [collection_id]
}
- )
+ if patient_id:
+ filters['PatientID'] = [patient_id]
+ if study_uid:
+ filters["StudyInstanceUID"] = [study_uid]
+ filter_query = build_solr_query(
+ filters,
+ with_tags_for_ex=False,
+ search_child_records_by=None, solr_default_op='AND'
+ )
+ result = query_solr_and_format_result(
+ {
+ "collection": source.name,
+ "fields": fields,
+ "query_string": None,
+ "fqs": [filter_query['full_query_str']],
+ "facets": None, "sort": None, "counts_only": False, "limit": MAX_FILE_LIST_REQUEST
+ }
+ )
+
for doc in result['docs']:
response['result'].append({
"series_id": doc['SeriesInstanceUID'],
@@ -703,8 +681,17 @@ def get_series(request, patient_id, study_uid=None):
"series_size": doc['instance_size'][0],
"modality": doc['Modality'][0],
"study_id": doc['StudyInstanceUID'],
- "case": doc["PatientID"]
+ "patient_id": doc["PatientID"],
+ "collection_id": doc['collection_id'][0]
})
+ if 'facets' in result:
+ response['download_stats'] = {
+ 'series_count': result['facets']['total_SeriesInstanceUID'],
+ 'study_count': result['facets']['total_StudyInstanceUID'],
+ 'collection_count': result['facets']['total_collection_id'],
+ 'case_count': result['facets']['total_PatientID'],
+ 'queue_byte_size': result['facets']['instance_size'],
+ }
except Exception as e:
logger.error("[ERROR] While fetching series per study ID:")
diff --git a/requirements.txt b/requirements.txt
index 7a16bc5b7..ce4e899cb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
cryptography==44.0.2
-django==4.2.24
+django==4.2.25
django-allauth==0.63.1
django-anymail[mailgun]
django-axes==5.40.0
diff --git a/shell/backup-solr.sh b/shell/backup-solr.sh
index 692848be3..885437ce0 100644
--- a/shell/backup-solr.sh
+++ b/shell/backup-solr.sh
@@ -20,7 +20,7 @@ do
echo "-h This help."
echo "-l Specifies a file which lists the cores to be backed up. Required if -c is not supplied."
echo "-c Specifies a list of comma-separated cores to back up. Required if -l is not supplied."
- echo "-f Specifies an output file name for the resulting TAR file. Optional. Default: solr_cores_backup_.tar"
+ echo "-f Specifies an output file name for the resulting TAR GZIP file. Optional. Default: solr_cores_backup_.tar.gz"
echo "-d Specifies a destination bucket for storing the resulting TAR output. Optional."
echo " "
echo "You must store the Solr API password in SOLR_PWD before running this script!"
diff --git a/shell/install-deps.sh b/shell/install-deps.sh
index 639eedb88..c98a2e4dc 100755
--- a/shell/install-deps.sh
+++ b/shell/install-deps.sh
@@ -38,8 +38,8 @@ apt-get update -qq
echo "[STATUS] Preparing System..."
apt-get -y --force-yes install software-properties-common ca-certificates gnupg
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv B7B3B788A8D3785C
-wget "https://repo.mysql.com/mysql-apt-config_0.8.30-1_all.deb" -P /tmp
-dpkg --install /tmp/mysql-apt-config_0.8.30-1_all.deb
+wget "https://repo.mysql.com/mysql-apt-config_0.8.35-1_all.deb" -P /tmp
+dpkg --install /tmp/mysql-apt-config_0.8.35-1_all.deb
apt-get update -qq
diff --git a/shell/restore-solr.sh b/shell/restore-solr.sh
index eafd796b0..a2961f5a3 100644
--- a/shell/restore-solr.sh
+++ b/shell/restore-solr.sh
@@ -73,7 +73,7 @@ for dirname in ${BACKUP_DIR}/*/; do
fi
if [ "$MAKE_CORE" = true ]; then
echo "[STATUS] Core creation enabled - attempting creation of core \"${CORE}\"..."
- sudo -u solr /opt/bitnami/solr/bin/solr create -c $CORE -s 2 -rf 2
+ sudo -u solr solr create -c $CORE
else
echo "[STATUS] Core creation disabled - this assumes core \"${CORE}\" exists already!"
echo " You'll see an error if it doesn't."
diff --git a/static/css/cart.css b/static/css/cart.css
index 0f449cbe8..619256ba8 100644
--- a/static/css/cart.css
+++ b/static/css/cart.css
@@ -19,3 +19,57 @@
.export-button:hover, .btnGroupDropViewers:hover {
cursor: pointer;
}
+
+.cart-filter-display {
+ margin-bottom: 8px;
+}
+
+.filter-type, .adjustment-type {
+ color: #a66206;
+ font-weight: bold;
+ text-transform: uppercase;
+ display: inline-block;
+}
+
+.filter-att{
+ background-color: #2A537A;
+ border-radius: 3px;
+ display: inline-block;
+ color: #F8F8F8;
+ padding: 2px;
+ margin: 0 3px 3px 3px
+}
+
+.filter-title, .adjustments-op {
+ font-weight: bold;
+}
+
+.adjustment-att {
+ color: #2A537A;
+ font-weight: bold;
+ display: inline-block;
+}
+
+.cart-def-list {
+ margin-bottom: 15px;
+ padding-bottom: 9px;
+}
+
+.cart-def-list:not(:last-child) {
+ border-bottom: 2px solid black;
+}
+
+.citations-button .fa-quote-left,.citations-button .fa-quote-right {
+ position: relative;
+ font-size: xx-small;
+}
+
+.citations-button .fa-quote-left {
+ top: -5px;
+ margin-left: 2px;
+}
+
+.citations-button .fa-quote-right {
+ top: 2px;
+ margin-left: -1px;
+}
\ No newline at end of file
diff --git a/static/css/search.css b/static/css/search.css
index 91a3760e2..395e97bd7 100644
--- a/static/css/search.css
+++ b/static/css/search.css
@@ -382,17 +382,25 @@ tr {
display: table;
width: 100%; }
-.expansion-toggle i {
+.table-striped > tbody > tr.entity-table-row.open {
+ background-color: #ffebce; !important
+}
+
+.expansion-toggle {
+ text-align: center;
font-size: larger;
}
.expansion-toggle.open {
- background-color: #c5b9d5;
+ background-color: #ffebce;
+}
+
+.table-striped > tbody > tr.entity-table-row:hover {
+ background-color: #b7cfe5;
}
.expansion-toggle:hover {
cursor: pointer;
- background-color: #b7cfe5;
}
.export-button:hover, .citations-button:hover, .btnGroupDropViewers:hover {
@@ -409,14 +417,16 @@ tr {
#cases_table_head th.ckbx:not(.cartnumholder), #cases_table td.ckbx:not(.cartnumholder),
#studies_table_head th.ckbx:not(.cartnumholder), #studies_table td.ckbx:not(.cartnumholder),
#series_table_head th.ckbx, #series_table td.ckbx {
- width: 5%;
+ width: 3%;
+ text-align: left !important;
+ min-width: 40px;
}
#search_def_stats {
padding-top: 8px; }
#projects_table td, #projects_table_head th {
- width: 30%;
+ width: 45%;
word-wrap: break-word; }
#cases_table td, #cases_table_head th {
@@ -473,9 +483,9 @@ tr {
#studies_table_head th.download-col,
#studies_table td.download-col, #cases_table_head th.download-col,
#cases_table td.download-col, #series_table_head th.download-col,
-#series_table td.download-col {
- width:6%;
- min-width: 80px;
+#series_table td.download-col, #proj_table th.download-col, #proj_table td.download-col {
+ width:3%;
+ min-width: 40px;
}
#series_table_head th, #series_table td {
@@ -781,8 +791,21 @@ li.cartlist {
}
}
-
#projects_table:hover, #cases_table:hover, #studies_table:hover{
cursor:pointer;
+}
+.citations-button .fa-quote-left,.citations-button .fa-quote-right {
+ position: relative;
+ font-size: xx-small;
}
+
+.citations-button .fa-quote-left {
+ top: -5px;
+ margin-left: 2px;
+}
+
+.citations-button .fa-quote-right {
+ top: 2px;
+ margin-left: -1px;
+}
\ No newline at end of file
diff --git a/static/css/style.css b/static/css/style.css
index b5675435a..0da2b11e5 100755
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -4523,9 +4523,12 @@ d-topics-list iframe {
cursor: pointer;
}
-.download-all-disabled {
- cursor: not-allowed;
- color: gray;
+.download-all-disabled, .is-disabled.download-instances {
+ cursor: not-allowed !important;
+}
+
+.fa.download-all-disabled, .fa.is-disabled {
+ color: gray;
}
#floating-message {
diff --git a/static/css/vendor.css b/static/css/vendor.css
index 7ed5c0d04..2a31d172f 100644
--- a/static/css/vendor.css
+++ b/static/css/vendor.css
@@ -2832,7 +2832,7 @@ select[multiple].input-lg,
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); }
.btn.disabled, .btn[disabled],
fieldset[disabled] .btn {
- cursor: not-allowed;
+ cursor: not-allowed !important;
opacity: 0.65;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
diff --git a/static/js/base.js b/static/js/base.js
index 9719b1079..61dfe4ede 100644
--- a/static/js/base.js
+++ b/static/js/base.js
@@ -276,6 +276,7 @@ define(['jquery', 'utils'], function($, utils) {
utils.removeCookie(name, path);
},
blockResubmit: utils.blockResubmit,
- checkManifestReady: utils.checkManifestReady
+ checkManifestReady: utils.checkManifestReady,
+ updateDownloadBtns: utils.updateDownloadBtns
};
});
diff --git a/static/js/cart_page.js b/static/js/cart_page.js
index 18455c748..40afc7efc 100644
--- a/static/js/cart_page.js
+++ b/static/js/cart_page.js
@@ -32,8 +32,7 @@ require.config({
cartutils: 'cartutils',
tippy: 'libs/tippy-bundle.umd.min',
'@popperjs/core': 'libs/popper.min',
-
-
+ citations_modal: 'citations_modal'
},
shim: {
'@popperjs/core': {
@@ -50,7 +49,8 @@ require.config({
'underscore': {exports: '_'},
'session_security': ['jquery'],
'cartutils': ['jquery'],
- 'export-manifest':['jquery']
+ 'export-manifest':['jquery'],
+ 'citations_modal': ['jquery']
}
});
@@ -64,17 +64,15 @@ require([
'base', // This must ALWAYS be loaded!
'jquerydt',
'jqueryui',
- 'bootstrap'
+ 'bootstrap',
+ 'citations_modal'
], function(cartutils, tables, $, tippy, _, base) {
- var ajaxtriggered = false;
+ var ajaxtriggered = false;
window.resetCartPageView = function(){
-
-
localStorage.setItem("cartcleared","yes");
window.history.back();
-
}
tippy.delegate('#cart-table', {
@@ -97,40 +95,48 @@ require([
var contentArray= [];
for (var i=0;i< window.cartHist.length;i++){
var cartDetail = window.cartHist[i];
- var filter = JSON.stringify(cartDetail.filter);
+ var filter = cartDetail.filter;
+ let is_filtered = (Object.keys(filter).length > 0);
+ let filter_match = is_filtered ? " which match the filter" : "";
+ let filter_div = "", adjustment_div = "";
var selections = cartDetail.selections;
if (selections.length>0){
- contentArray.push('Set filter definition set to: '+filter);
- for (var j=0;jAdded the series " +selec.sel[3] +" from collection " + selec.sel[0] +", case "+selec.sel[1]+", study "+selec.sel[2];
- }
- else{
- selectext += "Removed the series " +selec.sel[3] +" from collection " + selec.sel[0] +", case "+selec.sel[1]+", study "+selec.sel[2];
- }
-
+ if(is_filtered) {
+ let filter_set = [];
+ for(attr in filter) {
+ filter_set.push(`${attr} in (${filter[attr].map(d => ''+d+'').join("")})`);
}
- else {
- if (selec.added) {
- selectext += "Added series matching the filter from collection " + selec.sel[0]
- } else {
- selectext += "Removed series matching the filter from collection " + selec.sel[0]
- }
+ filter_div = `Filter: ${filter_set.join(" AND ")}
`;
+ }
+ let adjustments = [];
+ for (var j=0;j${op} series ${selec.sel[3]} from
+ collection ${selec.sel[0]},
+ case ${selec.sel[1]},
+ study ${selec.sel[2]}`;
+ } else {
+ selectext += `${op} series from
+ collection ${selec.sel[0]}`
if (selec.sel.length > 1) {
- selectext += ", case " + selec.sel[1]
+ selectext += `, case ${selec.sel[1]}`
}
if (selec.sel.length > 2) {
- selectext += ", study " + selec.sel[2]
+ selectext += `, study ${selec.sel[2]}`
}
+ selectext += filter_match
}
- contentArray.push(selectext);
+ adjustments.push(`${selectext}
`);
}
+ adjustment_div = `${adjustments.join("\n")}
`;
}
+ contentArray.push(`${filter_div}${adjustment_div}`)
}
- content = ""+contentArray.join('\n')+"
";
+ let content = `${contentArray.map(d => `- ${d}`).join("")}
`;
+ console.log(content);
$('#cart-description-modal').find('.modal-body').html(content);
}
@@ -150,7 +156,7 @@ require([
window.updatePartitionsFromScratch();
var ret =cartutils.formcartdata();
window.partitions = ret[0];
- window.filtergrp_lst = ret[1];
+ window.filtergrp_list = ret[1];
ajaxtriggered = true;
diff --git a/static/js/cartutils.js b/static/js/cartutils.js
index f75bdcafb..90065542c 100644
--- a/static/js/cartutils.js
+++ b/static/js/cartutils.js
@@ -122,45 +122,61 @@ define(['filterutils','jquery', 'tippy', 'base' ], function(filterutils, $, tip
return deferred.promise();
};
-
- const updateCartCounts =function(){
-
- var buttonContents = '';
-
- if (Object.keys(window.proj_in_cart).length>0){
+ const updateCartCounts = async function(){
+ var buttonContents = '';
+ let cart_has_contents = Boolean(Object.keys(window.proj_in_cart).length>0);
+ let cart_controls = $('.cart-activated-controls');
+ var nmseries = 0;
+ if (cart_has_contents){
var nmprojs = 0;
var nmcases=0;
var nmstudies=0;
- var nmseries =0;
for (projid in window.proj_in_cart){
nmprojs++;
nmcases=nmcases+window.proj_in_cart[projid]['cases'];
nmstudies=nmstudies+window.proj_in_cart[projid]['studies'];
nmseries=nmseries+window.proj_in_cart[projid]['series'];
}
-
-
- var content = buttonContents+'Cart contents: ' + nmseries.toString()+' series from '+nmprojs.toString()+
- ' collections / '+nmcases.toString()+' cases / '+nmstudies.toString()+' studies';
+ let content = buttonContents+'Cart contents: ' + nmseries.toString()+' series from '+nmprojs.toString()+
+ ' collections / '+nmcases.toString()+' cases / '+nmstudies.toString()+' studies (Calculating size...)';
localStorage.setItem('manifestSeriesCount',nmseries);
$('#cart_stats_holder').html(content) ;
$('#cart_stats').removeClass('empty-cart');
- $('#export-manifest-cart').removeAttr('disabled');
- $('.cart-view').removeAttr('disabled');
- $('.clear-cart').removeAttr('disabled');
- $('.clear-cart').on('click', function(){
- window.resetCart();
+ cart_controls.each(function(){
+ $(this).removeAttr('disabled');
+ !$(this).hasClass('tip-titled') && $(this).attr("title",$(this).attr("data-default-title"));
});
-
} else {
$('#cart_stats_holder').html('Your cart is currently empty');
$('#cart_stats').addClass('empty-cart');
-
- $('#export-manifest-cart').attr('disabled', 'disabled');
- $('.cart-view').attr('disabled', 'disabled');
- $('.clear-cart').attr('disabled', 'disabled');
+ cart_controls.each(function(){
+ $(this).attr('disabled', 'disabled');
+ !$(this).hasClass('tip-titled') && $(this).attr("title","Add items to the cart to enable this feature.");
+ });
+ }
+ let cart_disk_size = 0;
+ let cart_disk_display_size = "(Calculating...)";
+ let cart_disk_resp = await fetch(`${BASE_URL}/cart_data/`, {
+ method: "POST",
+ body: new URLSearchParams({
+ 'filtergrp_list': JSON.stringify(window.filtergrp_list ? window.filtergrp_list : [{}]),
+ 'partitions': JSON.stringify(window.partitions),
+ 'size_only': 'true'
+ }),
+ headers: {"X-CSRFToken": $.getCookie('csrftoken'), "content-type": 'application/x-www-form-urlencoded'}
+ });
+ if(!cart_disk_resp.ok) {
+ console.error("Unable to fetch cart size!");
+ cart_disk_size = 4;
+ cart_disk_display_size = "(Unable to retrieve size.)";
+ } else {
+ let cart_disk_res = await cart_disk_resp.json();
+ cart_disk_size = cart_disk_res['total_size']/Math.pow(1000,4);
+ cart_disk_display_size = cart_disk_res['display_size'];
}
+ $('.cart_disk_size').html(cart_disk_display_size);
+ base.updateDownloadBtns('cart', cart_has_contents, cart_disk_size, nmseries);
}
@@ -184,13 +200,9 @@ define(['filterutils','jquery', 'tippy', 'base' ], function(filterutils, $, tip
$('#cart_stats').addClass('empty-cart');
$('#cart_stats').html("Your cart is currently empty.");
- $('#export-manifest-cart').attr('disabled','disabled');
- $('.cart-view').attr('disabled','disabled');
- $('.clear-cart').attr('disabled','disabled');
+ $('.cart-activated-controls').attr('disabled','disabled');
}
-
-
//as user makes selections in the tables, record the selections in the cartHist object. Make new partitions from the selections
const updateCartSelections = function(newSel){
@@ -198,11 +210,8 @@ define(['filterutils','jquery', 'tippy', 'base' ], function(filterutils, $, tip
var selections = window.cartHist[curInd]['selections'];
selections.push(newSel);
window.cartHist[curInd]['partitions'] = mkOrderedPartitions(window.cartHist[curInd]['selections']);
-
-
}
-
// make partitions from table selections
const mkOrderedPartitions = function(selections){
parts = new Array();
@@ -317,7 +326,6 @@ define(['filterutils','jquery', 'tippy', 'base' ], function(filterutils, $, tip
window.updatePartitionsFromScratch = function(){
window.partitions = new Array();
-
for (var i=0;i 0) {
- solrA.push('(' + curPartStr + ')(' + curPartAttStrA[j] + ')')
- } else{
- solrA.push(curPartStr);
- }
- }
- }
- }
- solrA = solrA.map(x => '('+x+')');
- var solrStr = solrA.join(' OR ')
- return solrStr
- }
-
const parsePartitionAttStrings = function(filtStringA, partition){
var attStrA =[];
var filt2D = partition['filt'];
@@ -764,6 +745,10 @@ define(['filterutils','jquery', 'tippy', 'base' ], function(filterutils, $, tip
return newId;
}
+ $('.shopping-cart-panel').on('click', '.clear-cart', function(){
+ window.resetCart();
+ });
+
return {
mkOrderedPartitions: mkOrderedPartitions,
formcartdata: formcartdata,
diff --git a/static/js/citations_modal.js b/static/js/citations_modal.js
index cd99df381..c7f4e6a59 100644
--- a/static/js/citations_modal.js
+++ b/static/js/citations_modal.js
@@ -66,16 +66,37 @@ require([
A11y.Core();
- let csrftoken = $.getCookie('csrftoken')
+ let csrftoken = $.getCookie('csrftoken');
$('#citations-modal').on('show.bs.modal', async function(event) {
let button = $(event.relatedTarget);
- let dois = button.attr('data-dois').split("||");
let cites_list = $("#citations-modal .citations-list");
let copy_cites = $("#citations-modal .copy-this");
+
+ let dois = null;
cites_list.html(`
Formatting citation(s)...
`);
+ if(button.hasClass('for-cart')) {
+ let cart_result = await fetch(`${BASE_URL}/cart_data/`, {
+ method: "POST",
+ body: new URLSearchParams({
+ 'filtergrp_list': JSON.stringify(window.filtergrp_list ? window.filtergrp_list : [{}]),
+ 'partitions': JSON.stringify(window.partitions),
+ 'dois_only': 'true'
+ }),
+ headers: {"X-CSRFToken": csrftoken, "content-type": 'application/x-www-form-urlencoded'}
+ });
+ if(!cart_result.ok) {
+ cites_list.html("Failed to retrieve citations!");
+ throw new Error("Failed to retrieve citations!");
+ }
+ let data = await cart_result.json();
+ dois = data['dois'];
+ } else {
+ dois = button.attr('data-dois').split("||");
+ }
+
let dois_to_get = dois.filter((d) => (DOI_CACHE[d] === null || DOI_CACHE[d] === undefined));
if(dois_to_get.length > 0) {
let resp = null;
@@ -85,8 +106,10 @@ require([
body: JSON.stringify({
'doi': dois_to_get
}),
- headers: {"X-CSRFToken": csrftoken},
- "content-type": "application/json"
+ headers: {
+ "X-CSRFToken": csrftoken,
+ "content-type": "application/json"
+ }
});
} else {
let encoded_dois = dois_to_get.map(d => `doi=${encodeURIComponent(d)}`);
diff --git a/static/js/cohorts/export-manifest.js b/static/js/cohorts/export-manifest.js
index e47415551..56f0d50fb 100644
--- a/static/js/cohorts/export-manifest.js
+++ b/static/js/cohorts/export-manifest.js
@@ -75,29 +75,28 @@ require([
if (button.hasClass('series-export') || button.hasClass('study-export')){
update_export_modal_for_mini(button);
} else if (button.hasClass('cart-export')){
- updatePartitionsFromScratch();
window.updatePartitionsFromScratch();
- var ret =cartutils.formcartdata();
+ let ret = cartutils.formcartdata();
window.partitions = ret[0];
window.filtergrp_lst = ret[1];
- var projS = new Set();
+ let projS = new Set();
for (var i=0;i 1024) {
+ while(converted_size > 1000) {
byte_count += 1;
- converted_size /= 1024;
+ converted_size /= 1000;
}
let bytes = (Math.round(converted_size*1000)/1000).toFixed(3);
return `${bytes} ${byte_level[byte_count]}` ;
@@ -213,10 +219,14 @@ require([
queue_byte_size = 0;
bytes_downloaded = 0;
series_count = 0;
+ study_count = 0;
+ case_count = 0;
+ collection_count = 0;
start_time = -1;
collections = new Set([]);
cases = new Set([]);
studies = new Set([]);
+ preset_totals = false;
cancellation_underway = false;
@@ -251,12 +261,24 @@ require([
this.bytes_downloaded = 0;
this.series_count = 0;
this.collections = new Set([]);
+ this.collection_count = 0;
this.cases = new Set([]);
+ this.case_count = 0;
this.studies = new Set([]);
+ this.study_count = 0;
this.cancellation_underway = false;
this.start_time = -1;
}
+ set_download_totals(queue_byte_size, collection_count, case_count, study_count, series_count) {
+ this.queue_byte_size = queue_byte_size;
+ this.collection_count = collection_count;
+ this.case_count = case_count;
+ this.study_count = study_count;
+ this.series_count = series_count;
+ this.preset_totals = true;
+ }
+
get active_requests() {
return (this.working_queue.length > 0);
}
@@ -288,11 +310,16 @@ require([
let request_success = false;
if(this.hopper.length < this.HOPPER_LIMIT && !this.cancellation_underway) {
this.hopper.push(new DownloadRequest(request));
- this.queue_byte_size += parseFloat(request['series_size']);
- this.series_count += 1;
this.studies.add(request['study_id']);
this.collections.add(request['collection_id']);
this.cases.add(request['patient_id']);
+ if(!this.preset_totals) {
+ this.queue_byte_size += parseFloat(request['series_size']);
+ this.series_count += 1;
+ this.collection_count = this.collections.size;
+ this.study_count = this.studies.size;
+ this.case_count = this.cases.size;
+ }
request_success = true;
}
return request_success;
@@ -329,7 +356,8 @@ require([
let true_error = event.data.message === 'error' && event.data.error.name !== "AbortError";
let cancellation = downloader_manager.pending_cancellation || (event.data.message === 'error' && event.data.error.name === "AbortError");
if (true_error) {
- console.error(`Worker Error: ${JSON.stringify(event)}`);
+ console.error("Saw worker Error: ",event.data['error']);
+ console.error(event.data['text']);
downloader_manager.statusMessage(`Encountered an error while downloading these files.`, 'error', "error", true, false);
}
if (event.data.message === 'done' || cancellation) {
@@ -410,19 +438,22 @@ require([
const studyInstanceUID = metadata['study'];
const seriesInstanceUID = metadata['series'];
const fileName = metadata['instance'];
+ let response = null;
+ let fileHandle = null;
+ const seriesDirectory = modality + "_" + seriesInstanceUID;
+ const filePath = [collection_id, patientID, studyInstanceUID, seriesDirectory].join("/");
try {
const directoryHandle = event.data['directoryHandle'];
- const seriesDirectory = modality + "_" + seriesInstanceUID;
- const filePath = [collection_id, patientID, studyInstanceUID, seriesDirectory].join("/");
const subDirectoryHandle = await createNestedDirectories(directoryHandle, filePath);
- const fileHandle = await subDirectoryHandle.getFileHandle(fileName, {create: true});
+ fileHandle = await subDirectoryHandle.getFileHandle(fileName, {create: true});
const outputStream = await fileHandle.createWritable();
- let response = await fetch(s3_url, {
+ response = await fetch(s3_url, {
signal: abort_controller.signal
});
if (!response.ok) {
+ console.error("[Worker] Saw !ok response of ",response.status);
if(pending_abort) {
- console.log('User aborted downloads');
+ console.log('[Worker] User aborted downloads!');
} else {
console.error('[Worker] Failed to fetch URL: '+s3_url, response.statusText);
self.postMessage({message: "error", error: "Failed to fetch URL"});
@@ -451,14 +482,11 @@ require([
if(error.name === "AbortError" || (error.name === undefined && pending_abort)) {
msg = "Fetch was aborted. The user may have cancelled their downloads.";
} else {
- console.error("[Worker Error]", error.name);
- console.error("[Worker Error]", error.message);
- if(error.message.indexOf("getFileHandle") >= 0) {
- console.error("[Worker Error] File name attempted: ",fileName);
+ if((!fileHandle && error.name === "NotFoundError") || error.message.indexOf("getFileHandle") >= 0) {
+ msg = "Encountered an error while trying to create the file '"+filePath+"/"+fileName+"'";
}
- console.error("[Worker Error] on S3 target ", s3_url);
}
- self.postMessage({message: 'error', path: s3_url, error: error, 'text': msg});
+ self.postMessage({'message': 'error', 'path': s3_url, 'error': error, 'text': msg});
}
}
`;
@@ -487,9 +515,9 @@ require([
get all_requested() {
return `${this.queues.total_downloads_requested} requested in ` +
- `${this.queues.collections.size} collection(s) / ` +
- `${this.queues.cases.size} case(s) / ` +
- `${this.queues.studies.size} ${this.queues.studies.size <= 1 ? "study" : "studies"} / ` +
+ `${this.queues.collection_count} collection(s) / ` +
+ `${this.queues.case_count} case(s) / ` +
+ `${this.queues.study_count} ${this.queues.study_count <= 1 ? "study" : "studies"} / ` +
`${this.queues.series_count} series`;
}
@@ -509,6 +537,17 @@ require([
this.queues.bytes_downloaded += downloaded_amount;
}
+ pendingFetchMessage(fetch_type) {
+ // Don't display if we already have pending downloads
+ if(this.in_progress > 0) {
+ return;
+ }
+ let messages = {
+ 'content': `Fetching series IDs for ${fetch_type}...`
+ };
+ this.progressDisplay.show(null, messages, "seek", true, false);
+ }
+
// Replaces the current floating message contents with a new message, including a new icon if provided
statusMessage(message, type, icon, withClose, withCancel) {
let messages = {
@@ -635,6 +674,20 @@ require([
this.downloadWorkers.forEach(worker => {
worker.postMessage({'abort': true, 'reason': 'User cancelled download.'});
});
+ // Sometimes worker latency can cause a race condition between final cleanup and resetting the download
+ // manager's state. Run trigger a final time after waiting a couple of seconds to be totally sure
+ // we've cleaned up after ourselves.
+ setTimeout(2000, function(){
+ if(downloader_manager.pending_cancellation) {
+ downloader_manager.triggerWorkerDownloads();
+ }
+ });
+ }
+
+ set_download_stats(stats) {
+ this.queues.set_download_totals(
+ stats.queue_byte_size, stats.collection_count, stats.case_count, stats.study_count, stats.series_count
+ );
}
beginDownloads() {
@@ -651,11 +704,70 @@ require([
let downloader_manager = new DownloadManager();
+
+ async function handleLargeDownload(filter_and_cart) {
+ let series_job = await fetch(
+ `${SERIES_MANIFEST_URI}/`, {
+ method: "POST",
+ body: JSON.stringify(filter_and_cart),
+ headers: {
+ "X-CSRFToken": csrftoken,
+ "content-type": "application/json"
+ }
+ }
+ );
+ if(!series_job.ok) {
+ console.error("Unable to retrieve series IDs!");
+ return;
+ }
+ let job_result = await series_job.json();
+ downloader_manager.pendingFetchMessage("collection");
+
+ const MAX_ELAPSED_SERIES_IDS = 8000;
+ let polling = async function(file_name, check_start){
+ if(!check_start) {
+ check_start = Date.now();
+ }
+ let check_now = Date.now();
+ let elapsed_millis = check_now-check_start;
+ if((elapsed_millis) > MAX_ELAPSED_SERIES_IDS) {
+ console.error("Unable to retrieve series IDs!");
+ return;
+ }
+ let check_res = await fetch(`${CHECK_MANIFEST_URL}${file_name}/`);
+ if(!check_res.ok) {
+ console.error("Unable to retrieve series IDs!");
+ return;
+ }
+ let result = await check_res.json();
+ if(result.manifest_ready) {
+ let results = await(`${FETCH_MANIFEST_URL}${file_name}`);
+ if(!results.ok) {
+ console.error("Unable to retrieve series IDs!");
+ return;
+ }
+ const series_data = await response.json();
+ series.push(...series_data['result']);
+ } else {
+ setTimeout(polling,2000, file_name, check_start);
+ }
+ };
+ await polling(job_result.file_name);
+ }
+
+ $('.container-fluid').on('click', '.download-size-warning', function(){
+ const clicked = $(this);
+ let warning_button = $('#download-warning .download-all-instances');
+ warning_button.attr("data-collection", clicked.attr("data-collection"));
+ warning_button.attr("data-study", clicked.attr("data-study"));
+ warning_button.attr('data-patient', clicked.attr('data-patient'));
+ });
+
$('.container-fluid').on('click', '.cancel-download', function () {
downloader_manager.cancel();
});
- $('.container-fluid').on('click', '.download-all-instances', async function (event) {
+ $('body').on('click', '.download-all-instances', async function (event) {
// Stop propagation to the other handlers or the showDirectoryPicker will complain.
event.stopImmediatePropagation();
event.preventDefault();
@@ -668,17 +780,55 @@ require([
const collection_id = clicked.attr('data-collection');
const study_id = clicked.attr('data-study');
const patient_id = clicked.attr('data-patient');
+ const download_type = clicked.attr("data-download-type");
+ let csrftoken = $.getCookie('csrftoken');
let series = [];
- if(clicked.hasClass('download-study') || clicked.hasClass('download-case')) {
- let study_uri = (study_id !== undefined && study_id !== null) ? `${study_id}/` : "";
- let response = await fetch(`${BASE_URL}/series_ids/${patient_id}/${study_uri}`);
+ if(download_type !== "series") {
+ let response = null;
+ if(["cohort", "cart"].includes(download_type)) {
+ downloader_manager.pendingFetchMessage(download_type);
+ let filter_and_cart = {};
+ if (download_type === "cohort") {
+ let filtergrp_list = [];
+ for(const [attr, vals] of Object.entries(filterutils.parseFilterObj())) {
+ filtergrp_list.push({[attr]: vals});
+ }
+ filter_and_cart["filtergrp_list"] = filtergrp_list;
+ } else {
+ window.updatePartitionsFromScratch();
+ let ret = cartutils.formcartdata();
+ window.partitions = ret[0];
+ window.filtergrp_lst = ret[1];
+ let filterSets = [];
+ for(let i=0; i< window.cartHist.length;i++) {
+ filterSets.push(window.cartHist[i]['filter']);
+ }
+ filter_and_cart['partitions'] = window.partitions;
+ filter_and_cart['filtergrp_list'] = filterSets;
+ }
+ response = await fetch(`${BASE_URL}${SERIES_IDS_FILTER_URL}`, {
+ method: "POST",
+ body: JSON.stringify(filter_and_cart),
+ headers: {
+ "X-CSRFToken": csrftoken,
+ "content-type": "application/json"
+ }
+ });
+ } else {
+ let study_uri = (study_id !== undefined && study_id !== null) ? `${study_id}/` : "";
+ let patient_uri = (patient_id !== undefined && patient_id !== null) ? `${patient_id}/` : "";
+ response = await fetch(`${BASE_URL}${SERIES_IDS_URL}${collection_id}/${patient_uri}${study_uri}`);
+ }
if (!response.ok) {
- console.error(`[ERROR] Failed to retrieve series IDs for study ${study_id}: ${response.status}`);
+ console.error(`[ERROR] Failed to retrieve series IDs: ${response.status}`);
return;
}
const series_data = await response.json();
series.push(...series_data['result']);
+ if('download_stats' in series_data){
+ downloader_manager.set_download_stats(series_data['download_stats']);
+ }
} else {
series.push({
"bucket": clicked.attr('data-bucket'),
@@ -686,21 +836,14 @@ require([
"series_id": clicked.attr('data-series-id'),
"modality": clicked.attr('data-modality'),
"series_size": clicked.attr('data-series-size'),
- "study_id": study_id
+ "study_id": study_id,
+ "patient_id": patient_id,
+ "collection_id": collection_id
});
}
series.forEach(series_request => {
- downloader_manager.addRequest({
- 'directory': directoryHandle,
- 'bucket': series_request['bucket'],
- 'crdc_series_id': series_request['crdc_series_id'],
- 'series_id': series_request['series_id'],
- 'collection_id': collection_id,
- 'study_id': series_request['study_id'],
- 'modality': series_request['modality'],
- 'patient_id': patient_id,
- 'series_size': series_request['series_size']
- });
+ series_request['directory'] = directoryHandle;
+ downloader_manager.addRequest(series_request);
});
downloader_manager.beginDownloads();
});
diff --git a/static/js/explore.js b/static/js/explore.js
index 97225d44f..beb5d8724 100644
--- a/static/js/explore.js
+++ b/static/js/explore.js
@@ -73,9 +73,6 @@ require([
//sesssionStorage.setItem("cartHist",JSON.stringify(window.cartHist));
window.partitions = new Array();
- window.studymp=new Object();
- window.projstudymp = new Object();
- window.casestudymp = new Object();
window.collection = JSON.parse(document.getElementById('collections').textContent);
var lst = Object.keys(window.collection).sort();
window.collectionData = new Array();
@@ -84,10 +81,10 @@ require([
for (program in window.programs) {
for (project in window.programs[program]['projects']) {
- var col = project;
- var disp = window.programs[program]['projects'][project]['display'];
- var val = window.programs[program]['projects'][project]['val'];
- window.collectionData.push([col, disp, val, val]);
+ let id = project;
+ let disp = window.programs[program]['projects'][project]['display'];
+ let val = window.programs[program]['projects'][project]['val'];;
+ window.collectionData.push([id, disp, val, val]);
window.selProjects[project]=new Object();
}
}
@@ -399,7 +396,7 @@ require([
maxWidth: 130
});
- tippy('.bq-string-copy',{
+ const copy_tip = {
content: 'Copied!',
theme: 'blue',
placement: 'right',
@@ -412,57 +409,17 @@ require([
}, 1000);
},
maxWidth: 85
- });
+ };
- tippy.delegate('.series-table', {
- content: 'Copied!',
- theme: 'blue',
- placement: 'right',
- arrow: true,
- interactive: true, // This is required for any table tooltip to show at the appropriate spot!
- target: '.copy-this',
- onShow(instance) {
- setTimeout(function() {
- instance.hide();
- }, 1000);
- },
- trigger: "click",
- maxWidth: 85
- });
+ tippy('.bq-string-copy', copy_tip);
- tippy.delegate('.studies-table', {
- content: 'Copied!',
- theme: 'blue',
- placement: 'right',
- arrow: true,
- interactive: true, // This is required for any table tooltip to show at the appropriate spot!
- target: '.copy-this',
- onShow(instance) {
- setTimeout(function() {
- instance.hide();
- }, 1000);
- },
- trigger: "click",
- maxWidth: 85
- });
+ tippy.delegate('.series-table', copy_tip);
- tippy.delegate('.projects-table', {
- content: 'Copied!',
- theme: 'blue',
- placement: 'right',
- arrow: true,
- interactive: true, // This is required for any table tooltip to show at the appropriate spot!
- target: '.copy-this',
- onShow(instance) {
- setTimeout(function() {
- instance.hide();
- }, 1000);
- },
- trigger: "click",
- maxWidth: 85
- });
+ tippy.delegate('.studies-table', copy_tip);
+
+ tippy.delegate('.projects-table', copy_tip);
- const dynamicCartTip = {
+ const dynamicTipFunc = {
fn: (instance) => ({
onShow() {
instance.setContent(instance.props.dynamicTip(instance.reference));
@@ -470,22 +427,25 @@ require([
})
};
- tippy.delegate('.projects-table', {
+ const chromium_only = "Direct download is only available in Chromium browsers.";
+ const disabled_download_tooltip = {
+ plugins: [dynamicTipFunc],
dynamicTip: function(ref){
- if($(ref).parentsUntil('tbody').filter('tr').hasClass('extraInFilter') || !($(ref).parentsUntil('tbody').filter('tr').hasClass('someInCart'))) {
- return "add series to cart"
+ if($(ref).hasClass('download-size-disabled')) {
+ return "This set of images is over 3TB in size. Please use manifest download to obtain these images.";
}
- return "remove series from cart"
+ return chromium_only;
},
- interactive: true,
- allowHTML: true,
- placement: 'right',
- content: 'add series to cart', // placeholder text
- target: '.shopping-cart-holder',
- plugins: [dynamicCartTip]
- });
+ content: chromium_only,
+ theme: 'dark',
+ placement: 'left',
+ arrow: false,
+ interactive:true,
+ target: '.download-all-disabled',
+ maxWidth: 200
+ };
- tippy.delegate('.cases-table', {
+ const cart_tooltip = {
dynamicTip: function(ref){
if($(ref).parentsUntil('tbody').filter('tr').hasClass('extraInFilter') || !($(ref).parentsUntil('tbody').filter('tr').hasClass('someInCart'))) {
return "add series to cart"
@@ -495,147 +455,91 @@ require([
interactive: true,
allowHTML: true,
placement: 'right',
+ theme: 'dark',
content: 'add series to cart', // placeholder text
target: '.shopping-cart-holder',
- plugins: [dynamicCartTip]
- });
-
- tippy.delegate('.series-table', {
- dynamicTip: function(ref){
- if ($(ref).parentsUntil('tbody').filter('tr').hasClass('someInCart')) {
- return "remove from cart"
- }
- return "add to cart"
- },
- interactive: true,
- allowHTML: true,
- placement: 'right',
- content: 'add series to cart', // placeholder text
- target: '.shopping-cart-holder',
- plugins: [dynamicCartTip]
- });
+ plugins: [dynamicTipFunc]
+ };
- tippy.delegate('.studies-table', {
+ const manifest_tooltip = {
dynamicTip: function(ref){
- if($(ref).parentsUntil('tbody').filter('tr').hasClass('extraInFilter') || !($(ref).parentsUntil('tbody').filter('tr').hasClass('someInCart'))) {
- return "add series to cart"
+ let table_type = $(ref).parents('table').attr('data-table-type');
+ if(table_type === 'series') {
+ return 'Download a manifest file for this series.'
}
- return "remove series from cart"
+ return `Download a manifest file for all the series in this ${table_type}.`;
},
- interactive: true,
- allowHTML: true,
- placement: 'right',
- content: 'add series to cart', // placeholder text
- target: '.shopping-cart-holder',
- plugins: [dynamicCartTip]
- });
-
- tippy.delegate('.cases-table', {
- content: 'Copied!',
- theme: 'blue',
- placement: 'right',
- arrow: true,
- interactive: true, // This is required for any table tooltip to show at the appropriate spot!
- target: '.copy-this',
- onShow(instance) {
- setTimeout(function() {
- instance.hide();
- }, 1000);
- },
- trigger: "click",
- maxWidth: 85
- });
-
-
- tippy.delegate('.filter-display-panel', {
- content: 'Get the citation list for this cohort.',
+ content: 'Download a manifest file.', // placeholder text
theme: 'dark',
placement: 'left',
arrow: false,
interactive:true,
- target: '.citations-button',
- maxWidth: 200
- });
+ target: '.export-button',
+ maxWidth: 200,
+ plugins: [dynamicTipFunc]
+ };
- tippy.delegate('.cases-table', {
- content: 'Download all of the image instances in this case.',
+ let disabled_messages = {
+ 'download-all-disabled': chromium_only,
+ 'download-size-disabled': "This set of images is over 3TB in size. Please use manifest download to obtain these images.",
+ 'download-count-disabled': "This set of images contains over 65000 records. Please use manifest download to obtain these images.",
+ 'download-cart-disabled': "Add items to the cart to enable this feature.",
+ 'download-cohort-disabled': "Select a filter to enable this feature."
+ };
+ const download_tooltip = {
+ dynamicTip: function(ref){
+ if($(ref).hasClass('is-disabled')){
+ return disabled_messages[$(ref).attr('data-disabled-type')];
+ }
+ let download_type = $(ref).attr('data-download-type');
+ return `Download all of the image instances in this ${download_type}.`;
+ },
+ content: 'Download all images.', // placeholder text
theme: 'dark',
placement: 'left',
arrow: false,
interactive:true,
- target: '.download-all-instances',
- maxWidth: 200
- });
+ target: '.download-instances',
+ maxWidth: 200,
+ plugins: [dynamicTipFunc]
+ };
- tippy.delegate('.cases-table', {
- content: 'Direct download is only available in Chromium browsers.',
- theme: 'dark',
- placement: 'left',
- arrow: false,
- interactive:true,
- target: '.download-all-disabled',
- maxWidth: 200
- });
+ tippy.delegate('.projects-table', cart_tooltip);
- tippy.delegate('.studies-table', {
- content: 'Download all of the image instances in this study.',
- theme: 'dark',
- placement: 'left',
- arrow: false,
- interactive:true,
- target: '.download-all-instances',
- maxWidth: 200
- });
+ tippy.delegate('.cases-table', cart_tooltip);
- tippy.delegate('.studies-table', {
- content: 'Download a manifest file for this study.',
- theme: 'dark',
- placement: 'left',
- arrow: false,
- interactive:true,
- target: '.export-button',
- maxWidth: 200
+ tippy.delegate('.series-table', {
+ dynamicTip: function(ref){
+ if ($(ref).parentsUntil('tbody').filter('tr').hasClass('someInCart')) {
+ return "remove from cart"
+ }
+ return "add to cart"
+ },
+ interactive: true,
+ allowHTML: true,
+ placement: 'right',
+ content: 'add series to cart', // placeholder text
+ target: '.shopping-cart-holder',
+ plugins: [dynamicTipFunc]
});
- tippy.delegate('.studies-table', {
- content: 'Direct download is only available in Chromium browsers.',
- theme: 'dark',
- placement: 'left',
- arrow: false,
- interactive:true,
- target: '.download-all-disabled',
- maxWidth: 200
- });
+ tippy.delegate('.studies-table', cart_tooltip);
- tippy.delegate('.series-table', {
- content: 'Download all of the image instances in this series.',
- theme: 'dark',
- placement: 'left',
- arrow: false,
- interactive:true,
- target: '.download-all-instances',
- maxWidth: 200
- });
+ tippy.delegate('.cases-table', copy_tip);
- tippy.delegate('.series-table', {
- content: 'Download a manifest file for this series.',
+ tippy.delegate('#body', {
+ content: 'Get the citation list for this cohort.',
theme: 'dark',
placement: 'left',
arrow: false,
interactive:true,
- target: '.export-button',
+ target: '.citations-button',
maxWidth: 200
});
- tippy.delegate('.series-table', {
- content: 'Direct download is only available in Chromium browsers.',
- theme: 'dark',
- placement: 'left',
- arrow: false,
- interactive:true,
- target: '.download-all-disabled',
- maxWidth: 200
- });
+ tippy.delegate('.studies-table', manifest_tooltip);
+
+ tippy.delegate('.series-table', manifest_tooltip);
tippy.delegate('.series-table', {
content: 'Some or all of the images in this collection are not publicly available.',
@@ -659,6 +563,9 @@ require([
maxWidth: 800
});
+ tippy.delegate('#body', disabled_download_tooltip);
+ tippy.delegate('#body', download_tooltip);
+
tippy.delegate('#body', {
content: function(reference) {
return "Copied!";
@@ -677,10 +584,6 @@ require([
maxWidth: 85
});
- $('.download-link').on('click', function(){
- $('#download-images').modal("hide");
- });
-
$('.container-fluid').on('click', '.collapse-all', function(){
let targets = $(this).hasClass('search-scope-toggle') ? '.search-scope-item' : '.search-config-item';
$(targets).collapse('hide');
diff --git a/static/js/filterutils.js b/static/js/filterutils.js
index 226a3b754..75f63e70a 100644
--- a/static/js/filterutils.js
+++ b/static/js/filterutils.js
@@ -69,7 +69,7 @@ define(['jquery', 'base'], function($, base) {
$('.filter-activated-controls').each(function(){
if(!$(this).attr('data-pending-manifest')) {
$(this).attr("disabled","disabled");
- $(this).attr("title","Select a filter to enable this feature.");
+ !$(this).hasClass('tip-titled') && $(this).attr("title","Select a filter to enable this feature.");
}
})
$('.bq-string, .citations-list').html("");
@@ -89,7 +89,7 @@ define(['jquery', 'base'], function($, base) {
$('.filter-placeholder').hide();
$('.filter-activated-controls').each((function(){
if(!$(this).attr('data-pending-manifest')) {
- $(this).attr("title", $(this).attr("data-default-title"));
+ !$(this).hasClass('tip-titled') && $(this).attr("title", $(this).attr("data-default-title"));
$(this).removeAttr("disabled");
}
}));
@@ -643,7 +643,6 @@ define(['jquery', 'base'], function($, base) {
window.handleFilterSelectionUpdate(null, true, true);
}
-
window.handleFilterSelectionUpdate = function(filterElem, mkFilt, doUpdate) {
var promise = null
if (!(filterElem ===null)) {
diff --git a/static/js/image_search.js b/static/js/image_search.js
index 8b651980e..a89116619 100644
--- a/static/js/image_search.js
+++ b/static/js/image_search.js
@@ -86,13 +86,7 @@ require([
window.filterObj = {};
window.projIdSel = [];
window.studyIdSel = [];
- //window.tcgaColls = ["tcga_blca", "tcga_brca", "tcga_cesc", "tcga_coad", "tcga_esca", "tcga_gbm", "tcga_hnsc", "tcga_kich", "tcga_kirc", "tcga_kirp", "tcga_lgg", "tcga_lihc", "tcga_luad", "tcga_lusc", "tcga_ov", "tcga_prad", "tcga_read", "tcga_sarc", "tcga_stad", "tcga_thca", "tcga_ucec"];
window.projSets = new Object();
- window.projSets['tcga']=["tcga_blca", "tcga_brca", "tcga_cesc", "tcga_coad", "tcga_esca", "tcga_gbm", "tcga_hnsc", "tcga_kich", "tcga_kirc", "tcga_kirp", "tcga_lgg", "tcga_lihc", "tcga_luad", "tcga_lusc", "tcga_ov", "tcga_prad", "tcga_read", "tcga_sarc", "tcga_stad", "tcga_thca", "tcga_ucec"];
- window.projSets['rider']=["rider_lung_ct", "rider_phantom_pet_ct","rider_breast_mri", "rider_neuro_mri","rider_phantom_mri", "rider_lung_pet_ct"];
- window.projSets['qin'] = ["qin_headneck","qin_lung_ct","qin_pet_phantom","qin_breast_dce_mri"];
-
-
window.hidePanel=function(){
$('#lh_panel').hide();
@@ -103,7 +97,6 @@ require([
$('#rh_panel').addClass('col-lg-12');
$('#rh_panel').addClass('col-md-12');
};
-
window.showPanel=function(){
$('#lh_panel').show();
$('#show_lh').hide();
@@ -141,7 +134,7 @@ require([
success: function (data) {
try {
let curInd = window.cartHist.length-1;
- let tmp=filterutils.parseFilterObj()
+ let tmp=filterutils.parseFilterObj();
let filtObj=JSON.parse(JSON.stringify(tmp));
if (cartHist[curInd].selections.length>0){
let cartSel = new Object();
@@ -177,9 +170,9 @@ require([
async_download ? "True" : "False"
);
if('dois' in data) {
- $('.citations-button').attr("data-dois", Object.keys(data['dois']).join("||"));
+ $('.filter-display-panel .citations-button').attr("data-dois", Object.keys(data['dois']).join("||"));
} else {
- $('.citations-button').attr("data-dois", "");
+ $('.filter-display-panel .citations-button').attr("data-dois", "");
}
if (('filtered_counts' in data) && ('origin_set' in data['filtered_counts']) &&
('access' in data['filtered_counts']['origin_set']['All']['attributes']) &&
@@ -198,10 +191,15 @@ require([
data.totals.StudyInstanceUID.toString() + " studies (Size on disk: " +
data.totals.disk_size + ")"
);
+ base.updateDownloadBtns("cohort", true, data.totals.disk_size_tb, data.totals.SeriesInstanceUID);
} else if(isFiltered && data.total <= 0) {
$('#search_def_stats').html('There are no cases matching the selected set of filters');
+ base.updateDownloadBtns("cohort", false, 0, 0);
+ $('.citations-button').attr("disabled","disabled");
+ $('.citations-button').attr("title", "There are no cases matching the selected set of filters");
} else {
$('#search_def_stats').html(" ");
+ base.updateDownloadBtns("cohort", false, 0, 0);
}
filterutils.updateCollectionTotals('Program', data.programs);
@@ -568,67 +566,6 @@ require([
}, 0);
}
- updatecartedits = function(){
- if (("cartedits" in localStorage) && (localStorage.getItem("cartedits") == "true")) {
- var edits = window.cartHist[window.cartHist.length - 1]['selections'];
-
- var filt = Object();
- filt['StudyInstanceUID'] = new Array();
- var studymp = {};
- for (var i = 0; i < edits.length; i++) {
- var sel = edits[i]['sel'];
- var studyid = sel[2];
- filt['StudyInstanceUID'].push(studyid);
- var seriesid = sel[3];
- if (!(studyid in studymp)) {
- studymp[studyid] = []
- }
- studymp[studyid].push(seriesid)
- }
- if ("studymp" in sessionStorage) {
- var studymp = JSON.parse(sessionStorage.getItem("studymp"));
- for (studyid in studymp) {
- window.studymp[studyid]['val'] = studymp[studyid]
- }
- }
- if ("seriesdel" in sessionStorage) {
- window.seriesdel = JSON.parse(sessionStorage.getItem("seriesdel"));
- }
-
- cartutils.updateGlobalCart(false, studymp, 'series')
- window.updateTableCounts(1);
- var gtotals = cartutils.getGlobalCounts();
- var content = "Cart contents: " + gtotals[3]+" series from "+gtotals[0]+" collections / "+ gtotals[1]+" cases / "+gtotals[2]+ " studies";
-
- $('#cart_stats').html(content) ;
-
- if (gtotals[0]>0){
- $('#cart_stats').removeClass('empty-cart');
- $('.cart-view').removeAttr('disabled');
- $('.clear-cart').removeAttr('disabled');
- $('#export-manifest-cart').removeAttr('disabled');
- } else{
- $('#cart_stats').addClass('empty-cart');
- $('#cart_stats').html('Your cart is currently empty.');
- $('.cart-view').attr('disabled', 'disabled');
- $('#export-manifest-cart').attr('disabled','disabled');
- $('.clear-cart').attr('disabled','disabled');
- }
- }
- else if ("cartHist" in localStorage){
- localStorage.removeItem("cartHist");
- }
- if ("cartedits" in sessionStorage){
- sessionStorage.removeItem("cartedits");
- }
- if ("studymp" in sessionStorage){
- sessionStorage.removeItem("studymp");
- }
- if ("seriesdel" in sessionStorage) {
- sessionStorage.removeItem("seriesdel");
- }
- }
-
$(document).ready(async function () {
tables.initializeTableCacheData();
diff --git a/static/js/tables.js b/static/js/tables.js
index dabe05cd6..4dc9e1f49 100644
--- a/static/js/tables.js
+++ b/static/js/tables.js
@@ -213,7 +213,6 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
return cmp;
}
-
var dir = request.order[0]['dir'];
var colId = parseInt(request.order[0]['column']);
var col = cache.colOrder[colId];
@@ -237,12 +236,14 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
// classes for project(collection) table columns
var projectTableColDefs = function(){
- return [{className: "ckbx text_data viewbx caseview", "targets": [0]},
- {className: "ckbx shopping-cart-holder", "targets": [1]},
- {className: "ckbx cartnumholder", "targets": [2]},
- {className: "collex_name", "targets": [3]},
- {className: "collex-case-count table-count", "targets": [4]},
- {className: "projects_table_num_cohort table-count", "targets": [5]}];
+ return [
+ {className: "ckbx text_data viewbx caseview", "targets": [0]},
+ {className: "download-collection download-col", "targets": [1]},
+ {className: "ckbx shopping-cart-holder", "targets": [2]},
+ {className: "ckbx cartnumholder", "targets": [3]},
+ {className: "collex_name", "targets": [4]},
+ {className: "collex-case-count projects_table_num_cohort table-count", "targets": [5]}
+ ];
}
// project(collection) table column definitions
@@ -250,18 +251,18 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
var caret_col= { "type": "html", "orderable": false, render: function (data) {
if (('state' in window.selProjects[data]) && ('view' in window.selProjects[data]['state']) && (window.selProjects[data]['state']['view'] )) {
return ''+
- '' +
- ''
+ '' +
+ ''
} else {
return ''+
- '' +
- ''
+ '' +
+ ''
}
},
createdCell: function(td) {
$(td).attr("title", "Display cases in this collection below.");
$(td).addClass('expansion-toggle');
- if ($(td).find('.fa-caret-right.is-hidden').length > 0) {
+ if ($(td).find('.fa-folder.is-hidden').length > 0) {
$(td).addClass('open');
}
}
@@ -281,47 +282,55 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
' '
}};
- var case_col = { "type": "num", orderable: true };
- var dyn_case_col = {
- "type": "num",
- orderable: true,
- "createdCell": function (td, data, row) {
+ var case_col = { "type": "num", orderable: true, "createdCell": function (td, data, row) {
$(td).attr('id', 'patient_col_' + row[0]);
- return;
+ }, render: function(td, data,row) {
+ return `${row[4]} / ${row[5]}`;
+ }};
+ const download_col = {"type": "html", "orderable": false, data: 'collection_id', render: function(data, type, row) {
+ let download_size = window.collection[row[0]]['total_size_with_ar'];
+ if ("showDirectoryPicker" in window && download_size < 3) {
+ let download_classes = "download-collection download-instances";
+ download_classes = (download_size > 1) ? `${download_classes} download-size-warning` : `${download_classes} download-all-instances`;
+ let modal_attr = "";
+ if(download_size > 1) {
+ modal_attr = ` data-toggle="modal" data-target="#download-warning"`;
+ }
+ return ``;
+ } else {
+ let disabled_type = ("showDirectoryPicker" in window) ? "download-size-disabled" : "download_all_disabled";
+ return ``;
}
- };
+ }};
- return [caret_col, cart_col, cartnum_col, collection_col, case_col, dyn_case_col];
+ return [caret_col, download_col, cart_col, cartnum_col, collection_col, case_col];
}
const setRowCartClasses = function(row){
- if ( parseInt($(row).attr('series_in_cart'))>0)
- {
+ if ( parseInt($(row).attr('series_in_cart'))>0) {
$(row).addClass('someInCart');
- }
- else{
+ } else{
$(row).removeClass('someInCart');
}
- if ( parseInt($(row).attr('series_in_cart'))parseInt($(row).attr('series_in_filter_and_cart'))){
$(row).addClass('extraInFilter');
- }
- else{
+ } else{
$(row).removeClass('extraInFilter');
}
-
-
}
-
// creates the project or collection table on document load and after filtering. Defines the chevron and cart selection actions
window.updateProjectTable = function(collectionData,collectionStats, cartStats) {
var newCollectionData = []
@@ -345,7 +354,6 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
lclstats['studies_in_filter']=0
}
if (('series_per_collec' in collectionStats) && (projid in collectionStats['series_per_collec'])) {
- //stats['mxseries'] = collectionStats['series_per_collec'][projid]
lclstats['series_in_filter']=collectionStats['series_per_collec'][projid];
} else {
lclstats['mxseries'] = 0
@@ -373,26 +381,21 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
} else {
lclstats[item + '_in_filter_and_cart'] = 0;
}
-
- }
-
- else{
+ } else{
lclstats[item + '_in_cart'] = 0;
lclstats[item + '_in_filter_and_cart'] = 0;
//stats[item + '_in_filter'] = 0;
- }
+ }
}
-
var ncur=[cur[0], lclstats["series_in_cart"], cur[0], cur[1],cur[2],cur[3],lclstats];
newCollectionData.push(ncur);
}
-
$('#proj_table').DataTable().destroy();
$("#proj_table_wrapper").find('.dataTables_controls').remove();
var colDefs = projectTableColDefs();
var columns = projectTableColumns();
- var ord = [[3, "asc"]]
+ var ord = [[4, "asc"]]
$('#proj_table').DataTable(
{
@@ -400,10 +403,12 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
"order": ord,
"data": newCollectionData,
"createdRow": function (row, data, dataIndex) {
+ $(row).addClass('entity-table-row');
$(row).data('projectid', data[0]);
$(row).attr('data-projectid', data[0]);
$(row).data('totalcases', data[5]);
$(row).attr('totalcases', data[5]);
+ $(row).attr('data-total-series', data[6]['mxseries'])
$(row).attr('id', 'project_row_' + data[0]);
var projid = data[0];
// stats is created from the explorer data page when the page is first created
@@ -419,7 +424,6 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
$(row).on('click', function(event) {
handleRowClick("collections", row, event, [projid])
-
});
$(row).find('.collection_info').on("mouseenter", function(e){
@@ -487,13 +491,14 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
const caseTableColDefs = function(){
return [
{className: "ckbx studyview","targets": [0]},
- {className: "ckbx shopping-cart-holder", "targets": [1]},
- {className: "ckbx cartnumholder", "targets":[2]},
- {className: "col1 project-name wide", "targets": [3]},
- {className: "col1 case-id", "targets": [4]},
- {className: "col1 numrows narrow", "targets": [5]},
- {className: "col1 numseries narrow", "targets": [6]},
- {className: "col1 download-case download-col", "targets": [7]}];
+ {className: "col1 download-case download-col", "targets": [1]},
+ {className: "ckbx shopping-cart-holder", "targets": [2]},
+ {className: "ckbx cartnumholder", "targets":[3]},
+ {className: "col1 project-name wide", "targets": [4]},
+ {className: "col1 case-id", "targets": [5]},
+ {className: "col1 numrows narrow", "targets": [6]},
+ {className: "col1 numseries narrow", "targets": [7]}
+ ];
};
const caseTableColumns = function() {
@@ -503,19 +508,19 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
if ((collection_id in window.openCases) && (PatientID in window.openCases[collection_id]) ) {
//return ''
return ''+
- '' +
- ''
+ '' +
+ ''
} else {
return ''+
- '' +
- ''
+ '' +
+ ''
}
},
createdCell: function(td) {
$(td).attr("title", "Display studies in this case below.");
$(td).addClass('expansion-toggle');
- if ($(td).find('.fa-caret-right.is-hidden').length > 0) {
+ if ($(td).find('.fa-folder.is-hidden').length > 0) {
$(td).addClass('open');
}
}
@@ -551,16 +556,18 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
}};
const study_col = {"type": "num", "orderable": true, data: 'unique_studies'};
const series_col = {"type": "num", "orderable": true, data: 'unique_series'};
- const download_col = {"type": "htlp", "orderable": false, data: 'patient_id', render: function(data, type, row) {
+ const download_col = {"type": "html", "orderable": false, data: 'patient_id', render: function(data, type, row) {
if ("showDirectoryPicker" in window) {
- return ``;
}
- return ``;
+ return ``;
}};
- return [caret_col, cart_col, cartnum_col, collection_col, case_col, study_col, series_col, download_col];
+ return [caret_col, download_col, cart_col, cartnum_col, collection_col, case_col, study_col, series_col];
};
// recreates the cases table when a chevron is clicked in the projects table. Defines the chevron and cart selection actions.
@@ -584,6 +591,7 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
"dom": '<"dataTables_controls"ilp>rt<"bottom"><"clear">',
"order": [[4, 'asc'],[3, 'asc']],
"createdRow": function (row, data, dataIndex) {
+ $(row).addClass('entity-table-row');
$(row).attr('id', 'case_' + data['PatientID'])
$(row).attr('data-projectid', data['collection_id'][0]);
$(row).attr('data-caseid', data['PatientID']);
@@ -779,6 +787,7 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
"dom": '<"dataTables_controls"ilp>rt<"bottom"><"clear">',
"order": [[3, "asc"]],
"createdRow": function (row, data, dataIndex) {
+ $(row).addClass('entity-table-row');
$(row).attr('id', 'study_' + data['StudyInstanceUID'])
$(row).attr('data-studyid', data['StudyInstanceUID']);
$(row).attr('data-caseid', data['PatientID']);
@@ -853,16 +862,16 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
},
"columnDefs": [
{className: "ckbx seriesview", "targets": [0]},
- {className: "ckbx shopping-cart-holder", "targets": [1]},
- {className: "ckbx cartnumholder", "targets": [2]},
- {className: "col1 case-id", "targets": [3]},
- {className: "col2 study-id study-id-col study-id-tltp", "targets": [4]},
- {className: "col1 study-date", "targets": [5]},
- {className: "col1 study-description", "targets": [6]},
- {className: "col1 numrows", "targets": [7]},
- {className: "ohif open-viewer", "targets": [8]},
- {className: "manifest-col", "targets": [9]},
- {className: "download-col download-study", "targets": [10]},
+ {className: "download-col download-study", "targets": [1]},
+ {className: "ckbx shopping-cart-holder", "targets": [2]},
+ {className: "ckbx cartnumholder", "targets": [3]},
+ {className: "col1 case-id", "targets": [4]},
+ {className: "col2 study-id study-id-col study-id-tltp", "targets": [5]},
+ {className: "col1 study-date", "targets": [6]},
+ {className: "col1 study-description", "targets": [7]},
+ {className: "col1 numrows", "targets": [8]},
+ {className: "ohif open-viewer", "targets": [9]},
+ {className: "manifest-col", "targets": [10]}
],
"columns": [
{
@@ -875,25 +884,39 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
if ( (PatientID in window.openStudies) && (StudyInstanceUID in window.openStudies[PatientID])) {
//return ''
return ''+
- '' +
- ''
+ '' +
+ ''
} else {
return ''+
- '' +
- ''
+ '' +
+ ''
}
},
createdCell: function(td) {
$(td).attr("title", "Display series in this study below.");
$(td).addClass('expansion-toggle');
- if ($(td).find('.fa-caret-right.is-hidden').length > 0) {
+ if ($(td).find('.fa-folder.is-hidden').length > 0) {
$(td).addClass('open');
}
}
- },
- {
+ },{
+ "type":"html",
+ "orderable": false,
+ data: 'StudyInstanceUID', render: function (data, type, row) {
+ if ("showDirectoryPicker" in window) {
+ return ``;
+ }
+ return ``;
+ }
+ },{
"type": "html",
"orderable": false,
render: function () {
@@ -997,21 +1020,7 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
return ''
}
- },
- {
- "type":"html",
- "orderable": false,
- data: 'StudyInstanceUID', render: function (data, type, row) {
- if ("showDirectoryPicker" in window) {
- return ``;
- }
- return ``;
- }
- }
+ }
],
"processing": true,
"serverSide": true,
@@ -1152,6 +1161,7 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
"order": [[0, "asc"]],
"createdRow": function (row, data, dataIndex) {
$(row).attr('id', 'series_' + data['SeriesInstanceUID']);
+ $(row).addClass('entity-table-row');
$(row).attr('data-seriesid', data['SeriesInstanceUID']);
$(row).attr('data-studyid', data['StudyInstanceUID']);
$(row).attr('data-caseid', data['PatientID']);
@@ -1236,22 +1246,40 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
});
},
"columnDefs": [
- {className: "ckbx shopping-cart-holder", "targets": [0]},
- {className: "col1 study-id study-id-col study-id-tltp", "targets": [1]},
- {className: "series-id series-id-tltp", "targets": [2]},
- {className: "series-number", "targets": [3]},
- {className: "col1 modality", "targets": [4]},
- {className: "col1 body-part-examined", "targets": [5]},
- {className: "series-description", "targets": [6]},
- {className: "ohif open-viewer", "targets": [7]},
- {className: "manifest-col", "targets": [8]},
- {className: "download-col download-series", "targets": [9]}
+ {className: "download-col download-series", "targets": [0]},
+ {className: "ckbx shopping-cart-holder", "targets": [1]},
+ {className: "col1 study-id study-id-col study-id-tltp", "targets": [2]},
+ {className: "series-id series-id-tltp", "targets": [3]},
+ {className: "series-number", "targets": [4]},
+ {className: "col1 modality", "targets": [5]},
+ {className: "col1 body-part-examined", "targets": [6]},
+ {className: "series-description", "targets": [7]},
+ {className: "ohif open-viewer", "targets": [8]},
+ {className: "manifest-col", "targets": [9]},
],
"columns": [
- {"type": "html", "orderable": false, render: function () {
+ {
+ "type":"html",
+ "orderable": false,
+ data: 'SeriesInstanceUID', render: function (data, type, row){
+ if("showDirectoryPicker" in window) {
+ return ``
+ }
+ return ``
+ }
+ }, {
+ "type": "html", "orderable": false, render: function () {
return ''
}
- }, {
+ }, {
"type": "text", "orderable": true, data: 'StudyInstanceUID', render: function (data) {
return pretty_print_id(data) +
' '
}
- }, {
- "type":"html",
- "orderable": false,
- data: 'SeriesInstanceUID', render: function (data, type, row){
- if("showDirectoryPicker" in window) {
- return ``
- }
- return ``
- }
}
],
"processing": true,
@@ -1538,7 +1549,11 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
let refocus = function(){
setTimeout(function(){
- $(`#${input.attr('id')}`).focus();
+ let thisInput = $(`#${input.attr('id')}`);
+ thisInput.focus();
+ let val=thisInput.val();
+ thisInput.val("");
+ thisInput.val(val);
}, 300);
};
@@ -1940,10 +1955,9 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
}
const handleCartClick = function(tabletype, row, elem, ids){
- var updateElems =["series", "studies","cases"]
- //$(row).find('.shopping-cart-holder').trigger('shopping-cart:update-started');
- var addingToCart = true;
- if (tabletype=="series") {
+ let updateElems =["series", "studies","cases"];
+ let addingToCart = true;
+ if (tabletype==="series") {
if ($(elem).parentsUntil('tr').parent().hasClass('someInCart')) {
addingToCart = false;
} else {
@@ -1958,7 +1972,7 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
}
// record new selection
- var newSel={}
+ let newSel={};
newSel['added'] = addingToCart;
newSel['sel'] = ids;
cartutils.updateCartSelections(newSel);
@@ -2048,8 +2062,8 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
};
$('.addMult').on('click', function() {
- var idsattr=["data-projectid", "data-caseid", "data-studyid"]
- var idsArr=[]
+ var idsattr= ["data-projectid", "data-caseid", "data-studyid"];
+ var idsArr= [];
var tbl_head = $(this).parentsUntil('table').filter('thead');
var tbl_head_id = tbl_head.attr('id');
if ($(this).hasClass('fa-plus-circle')){
@@ -2081,12 +2095,14 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
idsArr.push(ids);
if (rowsAdded) {
- $(this).find('.expansion-toggle .fa-caret-right').addClass('is-hidden');
- $(this).find('.expansion-toggle .fa-caret-down').removeClass('is-hidden');
+ $(this).addClass('open');
+ $(this).find('.expansion-toggle .fa-folder').addClass('is-hidden');
+ $(this).find('.expansion-toggle .fa-folder-open').removeClass('is-hidden');
$(this).find('.viewbx').addClass('open');
} else{
- $(this).find('.expansion-toggle .fa-caret-right').removeClass('is-hidden');
- $(this).find('.expansion-toggle .fa-caret-down').addClass('is-hidden');
+ $(this).removeClass('open');
+ $(this).find('.expansion-toggle .fa-folder').removeClass('is-hidden');
+ $(this).find('.expansion-toggle .fa-folder-open').addClass('is-hidden');
$(this).find('.viewbx').removeClass('open');
}
});
@@ -2187,21 +2203,23 @@ define(['cartutils','filterutils','tippy','jquery', 'base'], function(cartutils,
}
// click anywhere else, open tables below
else {
- let toggle_elem = $(row).find('.expansion-toggle')
+ let toggle_elem = $(row).find('.expansion-toggle');
+ let rowsAdded = null;
if (toggle_elem.length>0) {
- var rowsAdded;
toggle_elem = toggle_elem[0];
$(toggle_elem).hasClass('open') ? $(toggle_elem).removeClass('open') : $(toggle_elem).addClass('open');
}
- if ($(row).find('.expansion-toggle .fa-caret-down.is-hidden').length>0){
- var rowsAdded = true;
- $(row).find('.expansion-toggle .fa-caret-right').addClass('is-hidden');
- $(row).find('.expansion-toggle .fa-caret-down').removeClass('is-hidden');
+ if ($(row).find('.expansion-toggle .fa-folder-open.is-hidden').length>0){
+ rowsAdded = true;
+ $(row).addClass('open');
+ $(row).find('.expansion-toggle .fa-folder').addClass('is-hidden');
+ $(row).find('.expansion-toggle .fa-folder-open').removeClass('is-hidden');
} else {
- var rowsAdded = false;
- $(row).find('.expansion-toggle .fa-caret-right').removeClass('is-hidden');
- $(row).find('.expansion-toggle .fa-caret-down').addClass('is-hidden');
+ rowsAdded = false;
+ $(row).removeClass('open');
+ $(row).find('.expansion-toggle .fa-folder').removeClass('is-hidden');
+ $(row).find('.expansion-toggle .fa-folder-open').addClass('is-hidden');
}
changeViewStates(tabletype, [ids], rowsAdded);
}
diff --git a/static/js/utils.js b/static/js/utils.js
index fbbbf0605..4ddb5fb11 100644
--- a/static/js/utils.js
+++ b/static/js/utils.js
@@ -41,6 +41,10 @@ require.config({
// Return an object for consts/methods used by most views
define(['jquery', 'jqueryui'], function($, jqueryui) {
+ // Terabyte and record count warning and cutoffs for download button tooltips and enabling
+ let DOWNLOAD_SIZE_LIMIT = 3;
+ let DOWNLOAD_SIZE_WARN = 2;
+ let DOWNLOAD_COUNT_LIMIT = 65000;
// Download block poll with cookie via StackOverflow:
// https://stackoverflow.com/questions/1106377/detect-when-browser-receives-file-download
@@ -142,6 +146,38 @@ define(['jquery', 'jqueryui'], function($, jqueryui) {
_hideFloatingMessage(true);
});
+ // type - string, ID selector subtype for the buttons and tooltips to adjust
+ // enabled - bool, sets if the button is to be disabled or enabled
+ // disk_size - size for determining size limit enabling and tooltip message, is assumed to be in TB
+ // requires setting DOWNLOAD_COUNT_LIMIT, DOWNLOAD_SIZE_LIMIT, and DOWNLOAD_SIZE_WARN
+ function _updateDownloadBtns(type, enabled, disk_size, record_count) {
+ let btn = $(`#download-${type}-images`);
+ let btn_tip = $(`#download-${type}-images-tooltips`);
+ let btn_and_tips = $(`#download-${type}-images-tooltips, #download-${type}-images-tooltips`);
+ if(!enabled){
+ btn_tip.addClass('is-disabled');
+ btn_tip.attr('data-disabled-type', `download-${type}-disabled`);
+ btn.attr("disabled", "disabled");
+ return;
+ }
+ if ("showDirectoryPicker" in window) {
+ btn_and_tips.removeClass('is-disabled download-all-instances download-size-warning');
+ if (disk_size > DOWNLOAD_SIZE_LIMIT || record_count > DOWNLOAD_COUNT_LIMIT) {
+ btn_tip.addClass('is-disabled');
+ btn.attr("disabled", "disabled");
+ btn_tip.attr('data-disabled-type', (disk_size > DOWNLOAD_SIZE_LIMIT ? "download-size-disabled" : "download-count-disabled"));
+ } else {
+ btn.removeAttr("disabled");
+ btn_tip.attr('data-disabled-type', "");
+ (disk_size > DOWNLOAD_SIZE_WARN) ? btn.addClass('download-size-warning') : btn.addClass('download-all-instances');
+ }
+ } else {
+ btn_tip.addClass('is-disabled');
+ btn_tip.attr('data-disabled-type', "download-all-disabled");
+ btn.attr("disabled", "disabled");
+ }
+ }
+
const MAX_ELAPSED = 240000;
function _checkManifestReady(file_name, check_start) {
if(!check_start) {
@@ -226,6 +262,7 @@ define(['jquery', 'jqueryui'], function($, jqueryui) {
getCookie: _getCookie,
removeCookie: _removeCookie,
checkManifestReady: _checkManifestReady,
- checkForManifest: _checkForManifest
+ checkForManifest: _checkForManifest,
+ updateDownloadBtns: _updateDownloadBtns
};
});
diff --git a/templates/base.html b/templates/base.html
index 65e2de6ff..fdc451676 100755
--- a/templates/base.html
+++ b/templates/base.html
@@ -60,6 +60,8 @@
const SLIM_VIEWER_PATH = '{{ SLIM_VIEWER_PATH }}';
const FETCH_MANIFEST_URL = "{% url 'fetch_user_manifest_base' %}";
const CHECK_MANIFEST_URL = "{% url 'check_user_manifest_base' %}";
+ const SERIES_IDS_URL = "{% url 'get_series_ids_base' %}";
+ const SERIES_IDS_FILTER_URL = "{% url 'get_series_ids_filter' %}";
@@ -104,6 +106,9 @@
style="display: none;">
+
+