diff --git a/sirepo/package_data/static/js/raydata.js b/sirepo/package_data/static/js/raydata.js
index a018f24ea5..1783445e65 100644
--- a/sirepo/package_data/static/js/raydata.js
+++ b/sirepo/package_data/static/js/raydata.js
@@ -446,7 +446,7 @@ SIREPO.app.directive('scansTable', function() {
PDF |
-
+
{{ column }}
|
@@ -502,10 +502,11 @@ SIREPO.app.directive('scansTable', function() {
let scanArgs = {
pageCount: 0,
pageNumber: 0,
- sortColumn: $scope.analysisStatus == 'queued' ? 'queue order' : 'start',
- sortOrder: $scope.analysisStatus == 'queued',
+ sortColumns: defaultSortColumns(),
};
+ const MAX_NUM_SORT_COLUMNS = 3;
+
const errorOptions = {
modelName: $scope.modelName,
onError: data => {
@@ -540,6 +541,14 @@ SIREPO.app.directive('scansTable', function() {
}
}
+ function defaultSortColumns() {
+ if ($scope.analysisStatus == 'queued') {
+ return [['queue order', true]];
+ } else {
+ return [['start', false]];
+ }
+ }
+
function findScan(scanId) {
return $scope.scans[
$scope.scans.findIndex(s => s.rduid === scanId)
@@ -579,8 +588,7 @@ SIREPO.app.directive('scansTable', function() {
}
scanArgs.pageCount = scanInfo.pageCount || 0;
scanArgs.pageNumber = scanInfo.pageNumber;
- scanArgs.sortColumn = scanInfo.sortColumn;
- scanArgs.sortOrder = scanInfo.sortOrder;
+ scanArgs.sortColumns = scanInfo.sortColumns;
updatePageLocation();
}
@@ -629,8 +637,7 @@ SIREPO.app.directive('scansTable', function() {
pageNumber: scanArgs.pageNumber,
searchText: m.searchText,
searchTerms: buildSearchTerms(m.searchTerms),
- sortColumn: scanArgs.sortColumn,
- sortOrder: scanArgs.sortOrder,
+ sortColumns: scanArgs.sortColumns
}
},
errorOptions
@@ -711,13 +718,29 @@ SIREPO.app.directive('scansTable', function() {
}
$scope.arrowClass = column => {
- if (scanArgs.sortColumn !== column) {
- return {};
+ let r = {};
+ scanArgs.sortColumns.forEach((sortField) => {
+ if (sortField[0] === column) {
+ r= {
+ glyphicon: true,
+ [`glyphicon-arrow-${sortField[1] ? 'up' : 'down'}`]: true,
+ };
+ }
+ });
+ return r;
+ };
+
+ $scope.arrowStyle = column => {
+ let r = '';
+ for (const [i, sortField] of scanArgs.sortColumns.entries()) {
+ if (sortField[0] === column) {
+ // logic for alpha value assumes that sortColumns are in descending priority and MAX_NUM_SORT_COLUMNS is 3
+ let a = i === 0 ? 1 : (-0.3 * i) + 0.8;
+ r = `color: rgba(0, 0, 0, ${a})`;
+ break;
+ }
}
- return {
- glyphicon: true,
- [`glyphicon-arrow-${scanArgs.sortOrder ? 'up' : 'down'}`]: true,
- };
+ return r;
};
$scope.canNextPage = () => {
@@ -843,15 +866,33 @@ SIREPO.app.directive('scansTable', function() {
};
$scope.columnIsSortable = (column) => {
- return column !== 'stop';
+ return ! (column === 'stop' || $scope.scans.length > 0 && $scope.scans[0][column] !== null && typeof $scope.scans[0][column] === 'object');
};
+
$scope.sortCol = column => {
if (! $scope.columnIsSortable(column)) {
return;
}
- scanArgs.sortColumn = column;
- scanArgs.sortOrder = ! scanArgs.sortOrder;
+ let inList = false;
+ scanArgs.sortColumns.forEach((sortField, i) => {
+ if (sortField[0] === column) {
+ inList = true;
+ sortField[1] = ! sortField[1];
+ scanArgs.sortColumns.splice(i, 1);
+ scanArgs.sortColumns.unshift(sortField);
+ }
+ });
+ if (! inList) {
+ if ($scope.analysisStatus !== 'allStatuses') {
+ scanArgs.sortColumns = [[column, true]];
+ } else {
+ scanArgs.sortColumns.unshift([column, true]);
+ if (scanArgs.sortColumns.length > MAX_NUM_SORT_COLUMNS) {
+ scanArgs.sortColumns.pop();
+ }
+ }
+ }
sendScanRequest(true, true);
};
diff --git a/sirepo/raydata/scan_monitor.py b/sirepo/raydata/scan_monitor.py
index d6c7a75295..4430fe247a 100644
--- a/sirepo/raydata/scan_monitor.py
+++ b/sirepo/raydata/scan_monitor.py
@@ -348,25 +348,6 @@ def _search_params(req_data):
}
return q
- def _sort_params(req_data):
- c = _default_columns(req_data.catalogName).get(
- req_data.sortColumn, req_data.sortColumn
- )
- s = [
- (
- c,
- pymongo.ASCENDING if req_data.sortOrder else pymongo.DESCENDING,
- ),
- ]
- if c != "time":
- s.append(
- (
- "time",
- pymongo.DESCENDING,
- )
- )
- return s
-
c = sirepo.raydata.databroker.catalog(req_data.catalogName)
pc = math.ceil(
len(
@@ -530,6 +511,24 @@ async def _sr_post(self, *args, **kwargs):
self.write(await self._incoming(PKDict(pkjson.load_any(self.request.body))))
+def _sort_params(req_data):
+ r = []
+ has_time = False
+ for x in req_data.sortColumns:
+ n = _default_columns(req_data.catalogName).get(x[0], x[0])
+ if n == "time":
+ has_time = True
+ r.append(
+ [
+ n,
+ pymongo.ASCENDING if x[1] else pymongo.DESCENDING,
+ ]
+ )
+ if not has_time:
+ r.append(["time", pymongo.DESCENDING])
+ return r
+
+
async def _init_analysis_processors():
global _ANALYSIS_PROCESSOR_TASKS
@@ -730,9 +729,10 @@ def _get_start_field(metadata, column):
def _scan_info_result(scans, page_count, req_data):
def _compare_values(v1, v2):
+ sort_column = _sort_params(req_data)[0][0]
# very careful compare - needs to account for missing values or mismatched types
- v1 = v1.get(req_data.sortColumn)
- v2 = v2.get(req_data.sortColumn)
+ v1 = v1.get(sort_column)
+ v2 = v2.get(sort_column)
if v1 is None and v2 is None:
return 0
if v1 is None:
@@ -765,7 +765,7 @@ def _compare_values(v1, v2):
s = sorted(
s,
key=functools.cmp_to_key(_compare_values),
- reverse=not req_data.sortOrder,
+ reverse=not _sort_params(req_data)[0][1],
)
return PKDict(
data=PKDict(
@@ -773,8 +773,7 @@ def _compare_values(v1, v2):
cols=_display_columns(all_columns),
pageCount=page_count,
pageNumber=req_data.pageNumber,
- sortColumn=req_data.sortColumn,
- sortOrder=req_data.sortOrder,
+ sortColumns=req_data.sortColumns,
)
)