Skip to content
Snippets Groups Projects
Select Git revision
  • 7a69ecfbf5f8fd68353bb9c52799f4aa78ff5d8e
  • main default protected
2 results

parse_dp3_output.py

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    gridcontroller.js 44.58 KiB
    // $Id: controller.js 32761 2015-11-02 11:50:21Z schaap $
    
    var gridControllerMod = angular.module('GridControllerMod', ['ui.grid',
                                                                 'ui.grid.edit',
                                                                 'ui.grid.selection',
                                                                 'ui.grid.resizeColumns',
                                                                 'ui.grid.autoResize']);
    
    gridControllerMod.controller('GridController', ['$scope', '$window', 'dataService', 'uiGridConstants', function($scope, $window, dataService, uiGridConstants) {
    
        var self = this;
    
        $scope.dataService = dataService;
    
        $scope.sanitize_url = function(in_url) {
            var split_url = in_url.split("://");
            return split_url[0] + "://" + split_url[1].replace(/\/+/g, "/");
        }
    
        $scope.selectBlockingPredecessors = function(in_blocking_predecessors) {
            $scope.$parent.$parent.loadTasksSelectAndJumpIntoView(in_blocking_predecessors);
        };
    
        $scope.openLtaLocation = function(in_ingest_tasks) {
            var ingest_tasks = Array.isArray(in_ingest_tasks) ? in_ingest_tasks : [in_ingest_tasks];
    
            //example: http://lofar.target.rug.nl/Lofar?mode=query_result_page_user&product=AveragingPipeline&ObservationId=544965&project=LC6_015
            //map task.sub_type to url product parameter
            var project2project2product2tasksDict = {};
    
            for(var t of ingest_tasks) {
                var lta_product;
                switch(t.sub_type) {
                    case 'averaging_pipeline': lta_product = 'AveragingPipeline'; break;
                    case 'calibration_pipeline': lta_product = 'CalibrationPipeline'; break;
                    case 'pulsar_pipeline': lta_product = 'PulsarPipeline'; break;
                    case 'lofar_imaging_pipeline': lta_product = 'ImagingPipeline'; break;
                    case 'imaging_pipeline_msss': lta_product = 'ImagingPipeline'; break;
                    case 'long_baseline_pipeline': lta_product = 'LongBaselinePipeline'; break;
                    case 'lofar_observation': lta_product = 'Observation'; break;
                }
                if(lta_product && (t.ingest_status != undefined) ) {
                    if(!project2project2product2tasksDict.hasOwnProperty(t.project_name)) {
                        project2project2product2tasksDict[t.project_name] = {};
                    }
                    if(!project2project2product2tasksDict[t.project_name].hasOwnProperty(lta_product)) {
                        project2project2product2tasksDict[t.project_name][lta_product] = [];
                    }
                    project2project2product2tasksDict[t.project_name][lta_product].push(t);
                }
            }
    
            var window_cntr = 0;
            for(var project in project2project2product2tasksDict) {
                for(var product in project2project2product2tasksDict[project]) {
                    var product_tasks = project2project2product2tasksDict[project][product];
                    var otdb_ids = product_tasks.map(function(pt) { return pt.otdb_id; });
                    var otdb_ids_string = otdb_ids.join(',');
                    var url = dataService.config.lta_base_url + '/Lofar?mode=query_result_page_user&product=' + product + '&ObservationId=' + otdb_ids_string + '&project=' + project;
                    url = $scope.sanitize_url(url);
                    setTimeout(function(url_arg) {
                        $window.open(url_arg, '_blank');
                    }, window_cntr*250, url);
                    window_cntr += 1;
                }
            }
        }
    
        $scope.columns = [
        { field: 'name',
            enableCellEdit: false,
            cellTooltip: function(row, col) { return row.entity.description; },
            width: '*',
            minWidth: '100',
        },
        { field: 'project_name',
            displayName:'Project',
            enableCellEdit: false,
            cellTemplate:'<div style=\'padding-top:5px;\'>' +
                         '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.project_mom2object_id}}"' +
                             ' title="{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}"' +
                         '>{{row.entity[col.field]}}' +
                         '</a></div>',
            width: '*',
            minWidth: '80',
            filter: {
                type: uiGridConstants.filter.SELECT,
                selectOptions: []
            }
        },
        { field: 'starttime',
            displayName: 'Start',
            width: '120',
            type: 'date',
            enableCellEdit: false,
            enableCellEditOnFocus: false,
            cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>',
            sort: { direction: uiGridConstants.ASC, priority: 3 }
        },
        { field: 'endtime',
            displayName: 'End',
            width: '120',
            type: 'date',
            enableCellEdit: false,
            enableCellEditOnFocus: false,
            cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | date:\'yyyy-MM-dd HH:mm:ss\'}}</div>'
        },
        { field: 'duration',
            displayName: 'Duration',
            width: '70',
            type: 'number',
            enableFiltering: false,
            enableCellEdit: false,
            enableCellEditOnFocus: false,
            cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity[col.field] | secondsToHHmmss}}</div>'
        },
        { field: 'status',
            enableCellEdit: false,
            width: '70',
            filter: {
                condition: uiGridConstants.filter.EXACT,
                type: uiGridConstants.filter.SELECT,
                selectOptions: []
            },
            cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) {
                return "grid-status-" + grid.getCellValue(row,col);
            }
            // Supposedly [1], select-box items can be formatted using:
            // headerCellFilter: 'statusFormatter'
            //
            // [1] http://stackoverflow.com/questions/37286945/ui-grid-setting-template-for-filter-options
        },
        {   field: 'info',
            displayName: 'Info',
            enableCellEdit: false,
            width: '45',
            filter: {
                condition: function(searchTerm, cellValue, row, column) {
                    var do_include = false;
                    switch(searchTerm) {
                        case 0: do_include = (row.entity.blocked_by_ids.length > 0);    break;
                        case 1: do_include = (row.entity.ingest_status=="ingesting");   break;
                        case 2: do_include = (row.entity.ingest_status=="ingested");    break;
                        case 3: do_include = (row.entity.ingest_status=="failed");      break;
                        case 4: do_include = row.entity.data_pinned;                    break;
                        default: break;
                    };
                    return do_include;
                },
                type: uiGridConstants.filter.SELECT,
                selectOptions: []
            },
            editableCellTemplate: 'ui-grid/dropdownEditor',
            editDropdownOptionsArray: [],
            headerTooltip: "Additional status information",
            cellTemplate:   '<div style="text-align: center" class="ui-grid-cell-contents">' +
                                '<span ng-if="row.entity.blocked_by_ids.length > 0"><img ng-click="row.grid.appScope.selectBlockingPredecessors(row.entity.blocked_by_ids)" ng-src="static/icons/blocked.png" title="Blocked by {{row.entity.blocked_by_ids.length.toString()}} predecessor(s) - Click to select" /></span>' +
                                '<span ng-if="row.entity.ingest_status==\'ingesting\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_in_progress.png" title="Ingest in progress - Click to open LTA catalog" /></span>' +
                                '<span ng-if="row.entity.ingest_status==\'ingested\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_successful.png" title="Ingest successful - Click to open LTA catalog" /></span>' +
                                '<span ng-if="row.entity.ingest_status==\'failed\'"><img ng-click="row.grid.appScope.openLtaLocation(row.entity)" ng-src="static/icons/ingest_failed.png" title="Ingest failed - Click to open LTA catalog" /></span>' +
                                '<span ng-if="row.entity.data_pinned"><img ng-src="static/icons/pinned.png" title="data is pinned and will not be deleted by (auto) cleanup service" /></span>' +
                            '</div>'
        },
        { field: 'type',
            enableCellEdit: false,
            width: '80',
            filter: {
                condition: uiGridConstants.filter.EXACT,
                type: uiGridConstants.filter.SELECT,
                selectOptions: []
            },
            sort: { direction: uiGridConstants.ASC, priority: 2 }
        },
        { field: 'disk_usage',
            displayName: 'Size',
            type: 'number',
            enableCellEdit: false,
            cellTemplate:'<div style=\'text-align:right; padding-top: 5px;\'>{{row.entity.disk_usage_readable}}</div>',
            width: '80',
            filter: {
                type: uiGridConstants.filter.SELECT,
                condition: uiGridConstants.filter.GREATER_THAN,
                selectOptions: [{ value:0, label: '> 0'}, { value:1e6, label: '> 1M'}, { value:1e9, label: '> 1G'}, { value:1e10, label: '> 10G'}, { value:1e11, label: '> 100G'}, { value:1e12, label: '> 1T'} ]
            }
        },
        { field: 'mom_object_group_id',
            displayName: 'Group ID',
            enableCellEdit: false,
            cellTemplate:'<div style=\'text-align: center; padding-top:5px;\'>' +
                            '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom_object_group_mom2object_id}}"' +
                                'title="' +
                                    'Group name: ' +           '{{row.entity.mom_object_group_name}}\n' +
                                    'Parent group name: ' +    '{{row.entity.mom_object_parent_group_name}}\n' +
                                    'Parent group ID: ' +      '{{row.entity.mom_object_parent_group_id}}' +
                                '">{{row.entity.mom_object_group_id}}</a></div>',
            width: '80',
            filter: {
                condition: uiGridConstants.filter.EXACT,
                type: uiGridConstants.filter.SELECT,
                selectOptions: []
            }
        },
        { field: 'mom_id',
            displayName: 'MoM ID',
            enableCellEdit: false,
            cellTemplate:'<div style=\'text-align: center; padding-top:5px;\'>' +
                            '<a target="_blank" href="https://lofar.astron.nl/mom3/user/project/setUpMom2ObjectDetails.do?view=generalinfo&mom2ObjectId={{row.entity.mom2object_id}}"' +
                                'title="' +
                                    'Project description: ' +  '{{row.grid.appScope.dataService.momProjectsDict[row.entity.project_mom_id].description}}\n' +
                                    'Task description: ' +     '{{row.entity.description}}\n' +
                                    'Group name: ' +           '{{row.entity.mom_object_group_name}}\n' +
                                    'Group ID: ' +             '{{row.entity.mom_object_group_id}}\n' +
                                    'Parent group name: ' +    '{{row.entity.mom_object_parent_group_name}}\n' +
                                    'Parent group ID: ' +      '{{row.entity.mom_object_parent_group_id}}' +
                                '">{{row.entity[col.field]}} </a></div>',
            width: '65'
        },
        { field: 'otdb_id',
            displayName: 'SAS ID',
            enableCellEdit: false,
            cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'>{{row.entity.otdb_id}}</div>',
            width: '65'
        },
        { field: 'id',
            displayName: 'RADB ID',
            enableCellEdit: false,
            cellTemplate:'<div style=\'text-align:center; padding-top:5px;\'><a target="_blank" href="tasks/{{row.entity.id}}.html">{{row.entity[col.field]}}</a></div>',
            width: '72'
        },
        { field: 'cluster',
            displayName: 'Cluster',
            enableCellEdit: false,
            width: '75',
            filter: {
                condition: uiGridConstants.filter.EXACT,
                type: uiGridConstants.filter.SELECT,
                selectOptions: []
            },
            cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) {
                var value = grid.getCellValue(row,col);
                return "grid-cluster-" + value;
            },
            sort: { direction: uiGridConstants.ASC, priority: 1 }
        }];
    
        if($scope.dataService.projectMode) {
            $scope.columns.splice(1, 1);
        }
    
        $scope.gridOptions = {
            enableGridMenu: false,
            enableSorting: true,
            enableFiltering: true,
            enableCellEdit: false,
            enableColumnResize: true,
            enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
            enableRowSelection: true,
            enableRowHeaderSelection: true,
            enableFullRowSelection: false,
            modifierKeysToMultiSelect: true,
            multiSelect:true,
            enableSelectionBatchEvent:false,
            gridMenuShowHideColumns: false,
            columnDefs: $scope.columns,
            data: [],
    //         rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell></div>"
            rowTemplate: "<div ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\" ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\" class=\"ui-grid-cell\" ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader }\" role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\" ui-grid-cell context-menu>",
            onRegisterApi: function(gridApi){
                $scope.gridApi = gridApi;
    
                $scope.gridApi.core.on.rowsRendered($scope, function() {
                    //on.rowsRendered is called whenever the data/filtering of the grid changed
                    //update the filteredTasks in the dataService from the resulting new grid rows
                    $scope.$evalAsync(function() {
                        var taskDict = $scope.dataService.taskDict;
                        $scope.dataService.filteredTasks = [];
                        var rows = $scope.gridApi.core.getVisibleRows(grid);
                        var numRows = rows.length;
                        for(var i = 0; i < numRows; i++) {
                            var row = rows[i];
                            if(row.visible)
                            {
                                var task_id = row.entity.id;
                                var task = taskDict[task_id];
                                if(task) {
                                    $scope.dataService.filteredTasks.push(task);
                                }
    
                                row.setSelected($scope.dataService.selected_task_ids.indexOf(task_id) != -1);
                            }
                        }
    
                        $scope.dataService.filteredTaskChangeCntr++;
    
                        if($scope.dataService.filteredTasks.length == 0) {
                            var otdb_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'otdb_id'; });
                            if(otdb_col && otdb_col.filters.length && otdb_col.filters[0].hasOwnProperty('term')) {
                                var otdb_id = otdb_col.filters[0].term;
                                $scope.$parent.$parent.loadTaskByOTDBIdSelectAndJumpIntoView(otdb_id).then(function(loadedTask) {
                                    if(loadedTask) {
                                        otdb_col.filters[0].term = null;
                                    }
                                });
                            } else {
                                var mom_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_id'; });
    
                                if(mom_col && mom_col.filters.length && mom_col.filters[0].hasOwnProperty('term')) {
                                    var mom_id = mom_col.filters[0].term;
                                    $scope.$parent.$parent.loadTaskByMoMIdSelectAndJumpIntoView(mom_id).then(function(task) {
                                        mom_col.filters[0].term = null;
                                        if(task == undefined) {
                                            //getting the task by mom_id did not find a task
                                            //maybe the entered id was a mom group_id?
                                            //let's try to loadTasksByMoMGroupIdSelectAndJumpIntoView
                                            $scope.$parent.$parent.loadTasksByMoMGroupIdSelectAndJumpIntoView(mom_id).then(function(tasks) {
                                                if(tasks == undefined || tasks.length == 0) {
                                                    //getting the tasks by mom group id did not find any tasks
                                                    //maybe the entered id was a mom parent group_id?
                                                    //let's try to loadTasksByMoMParentGroupIdSelectAndJumpIntoView
                                                    $scope.$parent.$parent.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(mom_id).then(function(tasks) {
                                                        //pass
                                                    });
                                                }
                                            });
                                        }
                                    });
                                }
                            }
                        }
                    });
                });
    
                gridApi.edit.on.afterCellEdit($scope,function(rowEntity, colDef, newValue, oldValue){
                    var task = $scope.dataService.taskDict[rowEntity.id];
                    var newTask = { id: task.id, status: task.status };
                    $scope.dataService.putTask(newTask);
                });
    
                gridApi.selection.on.rowSelectionChanged($scope,function(row){
                    if(row.entity.id) {
                        if(row.isSelected) {
                            $scope.dataService.addSelectedTaskId(row.entity.id);
                        } else if(!row.isSelected) {
                            $scope.dataService.removeSelectedTaskId(row.entity.id);
                        }
                    }
                });
            }
        };
    
        function fillColumFilterSelectOptions(options, columnDef) {
            if (columnDef == undefined)
                return;
    
            var columnSelectOptions = [];
            if(options) {
                for(var i = 0; i < options.length; i++) {
                    var option = options[i];
                    if(option.hasOwnProperty('value') && option.hasOwnProperty('label')) {
                        columnSelectOptions.push({ value: option.value, label: option.label })
                    }
                    else {
                        columnSelectOptions.push({ value: option, label: option })
                    }
                }
            }
    
            columnDef.filter.selectOptions = columnSelectOptions;
        };
    
        function populateListAsync() {
            $scope.$evalAsync(populateList);
        };
    
        function populateList() {
            if('tasks' in $scope.dataService && $scope.dataService.tasks.length > 0) {
                var viewFrom = $scope.dataService.viewTimeSpan.from;
                var viewTo = $scope.dataService.viewTimeSpan.to;
    
                var gridTasks = [];
    
                for(var task of $scope.dataService.tasks) {
                    if(task.endtime >= viewFrom && task.starttime <= viewTo) {
    
                        var gridTask = {
                            blocked_by_ids: task.blocked_by_ids,
                            cluster: task.cluster,
                            description: task.description,
                            disk_usage: task.disk_usage,
                            disk_usage_readable: task.disk_usage_readable,
                            duration: task.duration,
                            endtime: task.endtime,
                            id: task.id,
                            ingest_status: task.ingest_status,
                            mom2object_id: task.mom2object_id,
                            mom_id: task.mom_id,
                            mom_object_group_id: task.mom_object_group_id,
                            mom_object_group_mom2object_id: task.mom_object_group_mom2object_id,
                            mom_object_group_name: task.mom_object_group_name,
                            mom_object_parent_group_id: task.mom_object_parent_group_id,
                            name: task.name,
                            nr_of_dataproducts: task.nr_of_dataproducts,
                            otdb_id: task.otdb_id,
                            predecessor_ids: task.predecessor_ids,
                            project_mom2object_id: task.project_mom2object_id,
                            project_mom_id: task.project_mom_id,
                            project_name: task.project_name,
                            specification_id: task.specification_id,
                            starttime: task.starttime,
                            status: task.status,
                            status_id: task.status_id,
                            sub_type: task.sub_type,
                            successor_ids: task.successor_ids,
                            type: task.type,
                            type_id: task.type_id,
                            data_pinned: task.data_pinned
                        };
    
    
    
                        gridTasks.push(task);
                    }
                }
    
                $scope.gridOptions.data = gridTasks;
            } else
                $scope.gridOptions.data = []
    
            fillProjectsColumFilterSelectOptions()
            fillGroupsColumFilterSelectOptions();
        };
    
        function jumpToSelectedTaskRows() {
            var rowIndices = dataService.selected_task_ids.map(function(t_id) { return $scope.gridOptions.data.findIndex(function(row) {return row.id == t_id; } ); });
            rowIndices = rowIndices.filter(function(idx) {return idx > -1;}).sort();
    
            for(var rowIndex of rowIndices) {
                $scope.gridApi.core.scrollTo($scope.gridOptions.data[rowIndex], null);
            }
        };
    
        function onSelectedTaskIdsChanged() {
            var selected_task_ids = $scope.dataService.selected_task_ids;
            var rows = $scope.gridApi.grid.rows;
    
            for(var row of rows) {
                row.setSelected(selected_task_ids.indexOf(row.entity.id) != -1);
            }
    
            //find out if we have selected all tasks in a single mom group
            var selected_tasks = $scope.dataService.selected_task_ids.map(function(t_id) { return $scope.dataService.taskDict[t_id]; }).filter(function(t) { return t != undefined;});
    
            if(selected_tasks && selected_tasks.length > 1) {
                var selected_task_group_ids = selected_tasks.map(function(t) { return t.mom_object_group_id; });
                selected_task_group_ids = selected_task_group_ids.unique();
    
                if(selected_task_group_ids.length == 1) {
                    //we have selected tasks in a single mom group
                    //find out if we have selected all tasks within this mom group
                    var mom_object_group_id = selected_task_group_ids[0];
                    var all_group_tasks = $scope.dataService.tasks.filter(function(t) { return t.mom_object_group_id == mom_object_group_id; });
    
                    if(all_group_tasks.length == selected_tasks.length) {
                        //we have selected all tasks in a single mom group
                        //apply filter on group column to see only tasks within this group
                        var group_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_object_group_id'; });
                        if(group_col) {
                            var mom_object_group_name = all_group_tasks[0].mom_object_group_name;
                            var label = mom_object_group_id + ' ' + mom_object_group_name;
    
                            var groupSelectOptions = [ { value: mom_object_group_id, label: label} ];
    
                            fillColumFilterSelectOptions(groupSelectOptions, $scope.columns.find(function(c) {return c.field == 'mom_object_group_id'; }));
                            group_col.filters[0].term = mom_object_group_id;
                        }
                    }
                }
            }
    
    
            $scope.$evalAsync(jumpToSelectedTaskRows);
        };
    
        $scope.$watch('dataService.taskChangeCntr', function() { populateListAsync(); });
        $scope.$watch('dataService.claimChangeCntr', function() { populateListAsync(); });
        $scope.$watch('dataService.viewTimeSpan', function() {
            populateListAsync();
            $scope.$evalAsync(jumpToSelectedTaskRows);
        }, true);
    
        function fillFilterSelectOptions() {
            if(dataService.initialLoadComplete) {
                fillStatusColumFilterSelectOptions();
                fillInfoColumFilterSelectOptions();
                fillTypeColumFilterSelectOptions();
                fillProjectsColumFilterSelectOptions();
                fillGroupsColumFilterSelectOptions();
                fillColumFilterSelectOptions(['CEP2', 'CEP4', 'DRAGNET'], $scope.columns.find(function(c) {return c.field == 'cluster'; }));
            }
        };
    
        $scope.$watch('dataService.filteredTaskChangeCntr', function()  { $scope.$evalAsync(fillFilterSelectOptions()); });
        $scope.$watch('dataService.initialLoadComplete',    function()  {
            populateListAsync();
            $scope.$evalAsync(fillFilterSelectOptions()); });
    
        function fillProjectsColumFilterSelectOptions() {
            var projectNames = [];
            var momProjectsDict = $scope.dataService.momProjectsDict;
            var tasks = $scope.dataService.filteredTasks;
    
            var project_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'project_name'; });
            if(project_col && project_col.filters.length && project_col.filters[0].term) {
                tasks = $scope.dataService.tasks;
            }
    
            //get unique projectIds from tasks
            var task_project_ids = tasks.map(function(t) { return t.project_mom_id; });
            task_project_ids = task_project_ids.unique();
    
            for(var project_id of task_project_ids) {
                if(momProjectsDict.hasOwnProperty(project_id)) {
                    var projectName = momProjectsDict[project_id].name;
                    if(!(projectName in projectNames)) {
                        projectNames.push(projectName);
                    }
                }
            }
            projectNames.sort();
            fillColumFilterSelectOptions(projectNames, $scope.columns.find(function(c) {return c.field == 'project_name'; }));
        };
    
        function fillStatusColumFilterSelectOptions() {
            var tasks = $scope.dataService.filteredTasks;
    
            var status_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'status'; });
            if(status_col && status_col.filters.length && status_col.filters[0].term) {
                tasks = $scope.dataService.tasks;
            }
    
            //get unique statuses from tasks
            var task_statuses = tasks.map(function(t) { return t.status; });
            task_statuses = task_statuses.unique();
            task_statuses.sort();
    
            fillColumFilterSelectOptions(task_statuses, $scope.columns.find(function(c) {return c.field == 'status'; }));
        };
    
        function fillTypeColumFilterSelectOptions() {
            var tasks = $scope.dataService.filteredTasks;
    
            var type_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'type'; });
            if(type_col && type_col.filters.length && type_col.filters[0].term) {
                tasks = $scope.dataService.tasks;
            }
    
            //get unique types from tasks
            var task_types = tasks.map(function(t) { return t.type; });
            task_types = task_types.unique();
            task_types.sort();
    
            fillColumFilterSelectOptions(task_types, $scope.columns.find(function(c) {return c.field == 'type'; }));
        };
    
        function fillGroupsColumFilterSelectOptions() {
            var group_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'mom_object_group_id'; });
            if(!group_col || group_col.filter.term) {
                return;
            }
    
            var tasks = $scope.dataService.filteredTasks;
    
            //get unique groupNames from tasks
            var groupId2Name = {};
            var groupIds = [];
    
            for(var task of tasks) {
                if(task.mom_object_group_id) {
                    if(!groupId2Name.hasOwnProperty(task.mom_object_group_id)) {
                        groupId2Name[task.mom_object_group_id] = task.mom_object_group_name;
                        groupIds.push(task.mom_object_group_id);
                    }
                }
            }
    
            groupIds.sort();
    
            fillColumFilterSelectOptions(groupIds, $scope.columns.find(function(c) {return c.field == 'mom_object_group_id'; }));
        };
    
        function fillInfoColumFilterSelectOptions() {
            var tasks = $scope.dataService.filteredTasks;
    
            var info_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'info'; });
            if(info_col && info_col.filters.length && info_col.filters[0].term) {
                tasks = $scope.dataService.tasks;
            }
    
            // Generate a list of unique information items
            var task_info = [];
            var info_bit_flags = 0x00;
            for(var task of tasks) {
                if((task.blocked_by_ids.length > 0) && !(info_bit_flags & 0x01)) {
                    task_info.push({ value: 0, label: 'Blocked tasks' });
                    info_bit_flags |= 0x01;
                }
    
                if((task.ingest_status === 'ingesting') && !(info_bit_flags & 0x02)) {
                    task_info.push({ value: 1, label: 'Ingests in progress' });
                    info_bit_flags |= 0x02;
                }
    
                if((task.ingest_status === 'ingested') && !(info_bit_flags & 0x04)) {
                    task_info.push({ value: 2, label: 'Successful ingests' });
                    info_bit_flags |= 0x04;
                }
    
                if((task.ingest_status === 'failed') && !(info_bit_flags & 0x08)) {
                    task_info.push({ value: 3, label: 'Failed ingests' });
                    info_bit_flags |= 0x08;
                }
    
                if(task.data_pinned && !(info_bit_flags & 0x10)) {
                    task_info.push({ value: 4, label: 'Pinned' });
                    info_bit_flags |= 0x10;
                }
            };
    
            // sort on key values
            function keysrt(key,desc) {
              return function(a,b){
               return desc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]);
              }
            }
    
            task_info.sort(keysrt('value'));
            fillColumFilterSelectOptions(task_info, $scope.columns.find(function(c) {return c.field == 'info'; }));
        };
    
        $scope.$watch('dataService.selected_task_ids', onSelectedTaskIdsChanged, true);
        $scope.$watch('dataService.selected_project_id', function() {
            fillProjectsColumFilterSelectOptions();
    
            var project_col = $scope.gridApi.grid.columns.find(function(c) {return c.field == 'project_name'; });
            if(project_col && project_col.filters.length) {
                if(dataService.selected_project_id != undefined) {
                    var projectName = dataService.momProjectsDict[dataService.selected_project_id].name;
                    if(projectName != undefined) {
                        var project_names = project_col.filter.selectOptions.map(function(so) { return so.value;});
                        if(project_names.includes(projectName)) {
                            project_col.filters[0].term = projectName;
                        }
                    }
                }
            }
        });
    }]);
    
    gridControllerMod.directive('contextMenu', ['$document', '$window', function($document, $window) {
        return {
          restrict: 'A',
          scope: {
          },
          link: function($scope, $element, $attrs) {
            function handleContextMenuEvent(event) {
                //pragmatic 'hard-coded' way of getting the dataService and the rowEntity via scope tree.
                var dataService = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.dataService;
                var cleanupCtrl = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.cleanupCtrl;
                var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent;
                var row = $scope.$parent.$parent.$parent.row;
                var rowEntity = row.entity;
    
                if(!dataService || !rowEntity)
                    return true;
    
                var taskId = rowEntity.id;
                var task = dataService.taskDict[taskId];
                if(!task)
                    return true;
    
                if(!dataService.isTaskIdSelected(taskId)) {
                    dataService.setSelectedTaskId(taskId);
                }
    
                var docElement = angular.element($document);
    
                //search for already existing contextmenu element
                while($document.find('#grid-context-menu').length) {
                    //found, remove it, so we can create a fresh one
                    $document.find('#grid-context-menu')[0].remove();
    
                    //unbind document close event handlers
                    docElement.unbind('click', closeContextMenu);
                    docElement.unbind('contextmenu', closeContextMenu);
                }
    
                //create contextmenu element
                //with list of menu items,
                //each with it's own action
                var contextmenuElement = angular.element('<div id="grid-context-menu"></div>');
                var ulElement = angular.element('<ul class="dropdown-menu" role="menu" style="left:' + event.clientX + 'px; top:' + event.clientY + 'px; z-index: 100000; display:block;"></ul>');
                contextmenuElement.append(ulElement);
    
                var selected_tasks = dataService.selected_task_ids.map(function(t_id) { return dataService.taskDict[t_id]; });
                selected_tasks = selected_tasks.filter(function(t) { return t != undefined; });
                var selected_cep4_tasks = selected_tasks.filter(function(t) { return t['cluster'] == 'CEP4'; });
    
    //                             var liElement = angular.element('<li><a href="#">Copy Task</a></li>');
    //                             ulElement.append(liElement);
    //                             liElement.on('click', function() {
    //                                 closeContextMenu();
    //                                 //TODO: remove link to dataService in this generic plugin
    //                                 dataService.copyTask(task);
    //                             });
    
                var liElement = angular.element('<li><a href="#">Select group</a></li>');
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();
                    dataCtrlScope.loadTasksByMoMGroupIdSelectAndJumpIntoView(task.mom_object_group_id);
                });
    
                var liElement = angular.element('<li><a href="#">Select parent group</a></li>');
                ulElement.append(liElement);
                liElement.on('click', function() {
                    closeContextMenu();
                    dataCtrlScope.loadTasksByMoMParentGroupIdSelectAndJumpIntoView(task.mom_object_parent_group_id);
                });
    
                var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); });
    
                if(blocked_selected_cep4_tasks.length > 0) {
                    var liContent = '<li><a href="#">Select blocking predecessor(s)</a></li>'
                    var liElement = angular.element(liContent);
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
    
                        var blocking_predecessors = []
                        for(var task of blocked_selected_cep4_tasks) {
                            blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids);
                        }
                        row.grid.appScope.selectBlockingPredecessors(blocking_predecessors);
                    });
                }
    
                var completed_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'finished' || t.status == 'aborted'; });
                var completed_selected_cep4_observations = completed_selected_cep4_tasks.filter(function(t) { return t.type == 'observation'; });
    
                if(completed_selected_cep4_observations.length > 0 && dataService.config.inspection_plots_base_url) {
                    var liElement = angular.element('<li><a href="#">Inspection Plots</a></li>');
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
    
                        var window_cntr = 0;
                        for(var obs of completed_selected_cep4_observations) {
                            var url = dataService.config.inspection_plots_base_url + '/' + obs.otdb_id;
                            url = row.grid.appScope.sanitize_url(url);
                            setTimeout(function(url_arg) {
                                $window.open(url_arg, '_blank');
                            }, window_cntr*750, url);
                            window_cntr += 1;
                        }
                    });
                }
    
                var ingest_tasks = selected_tasks.filter(function(t) { return t.ingest_status != undefined; });
    
                if(ingest_tasks.length > 0 && dataService.config.lta_base_url) {
                    var liElement = angular.element('<li><a href="#">Open in LTA catalogue</a></li>');
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
                        row.grid.appScope.openLtaLocation(ingest_tasks);
                    });
                }
    
                var liContent = selected_tasks.length > 0 ? '<li><a href="#">Show parset</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show parset</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                if(selected_tasks.length > 0) {
                    liElement.on('click', function() {
                        closeContextMenu();
    
                        var window_cntr = 0;
                        for(var selected_task of selected_tasks) {
                            var url = 'rest/tasks/otdb/' + selected_task.otdb_id + '/parset';
                            setTimeout(function(url_arg) {
                                $window.open(url_arg, '_blank');
                            }, window_cntr*750, url);
                            window_cntr += 1;
                        }
    
                    });
                }
    
                var liContent = selected_cep4_tasks.length > 0 ? '<li><a href="#">Show disk usage</a></li>' : '<li><a href="#" style="color:#aaaaaa">Show disk usage</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                if(selected_cep4_tasks.length > 0) {
                    liElement.on('click', function() {
                        closeContextMenu();
                        if(selected_cep4_tasks.length == 1) {
                            cleanupCtrl.showTaskDiskUsage(task);
                        } else {
                            cleanupCtrl.showTaskDiskUsage(completed_selected_cep4_tasks);
                        }
                    });
                }
    
                var unpinned_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return !t.data_pinned; });
    
                var liContent = unpinned_selected_cep4_tasks.length > 0 ? '<li><a href="#">Delete data</a></li>' : '<li><a href="#" style="color:#aaaaaa">Delete data</a></li>'
                var liElement = angular.element(liContent);
                ulElement.append(liElement);
                if(unpinned_selected_cep4_tasks.length > 0) {
                    liElement.on('click', function() {
                        closeContextMenu();
                        cleanupCtrl.deleteTasksDataWithConfirmation(unpinned_selected_cep4_tasks);
                    });
                }
    
                if(unpinned_selected_cep4_tasks.length > 0) {
                    var liContent = '<li><a href="#">Pin task data against (auto) cleanup</a></li>';
                    var liElement = angular.element(liContent);
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
    
                        for(var t of unpinned_selected_cep4_tasks) {
                            var newTask = { id: t.id, data_pinned: true };
                            dataService.putTask(newTask);
                        }
                    });
                }
    
                var pinned_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.data_pinned; });
    
                if(pinned_selected_cep4_tasks.length > 0) {
                    var liContent = '<li><a href="#">Unpin task data (allow (auto) cleanup to delete)</a></li>';
                    var liElement = angular.element(liContent);
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
    
                        for(var t of pinned_selected_cep4_tasks) {
                            var newTask = { id: t.id, data_pinned: false };
                            dataService.putTask(newTask);
                        }
                    });
                }
    
                var schedulable_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'approved' || t.status == 'conflict' || t.status == 'error'; });
    
                if(schedulable_cep4_tasks.length > 0) {
                    var liContent = '<li><a href="#">Schedule CEP4 task(s)</a></li>'
                    var liElement = angular.element(liContent);
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
                        for(var pl of schedulable_cep4_tasks) {
                            var newTask = { id: pl.id, status: 'prescheduled' };
                            dataService.putTask(newTask);
                        }
                    });
                }
    
                var unschedulable_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return t.status == 'prescheduled' || t.status == 'scheduled' || t.status == 'queued' || t.status == 'error'; });
    
                if(unschedulable_selected_cep4_tasks.length > 0) {
                    var liContent = '<li><a href="#">Unschedule (pre)scheduled/queued/error tasks</a></li>'
                    var liElement = angular.element(liContent);
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
                        for(var pl of unschedulable_selected_cep4_tasks) {
                            if(pl.status == 'queued') {
                                var newTask = { id: pl.id, status: 'aborted' };
                                dataService.putTask(newTask);
                            }
    
                            var newTask = { id: pl.id, status: 'approved' };
                            dataService.putTask(newTask);
                        }
                    });
                }
    
                var active_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'active' || t.status == 'completing') && t.type == 'pipeline'; });
    
                if(active_selected_cep4_pipelines.length > 0) {
                    var liContent = '<li><a href="#">Abort active CEP4 pipelines</a></li>'
                    var liElement = angular.element(liContent);
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
                        for(var pl of active_selected_cep4_pipelines) {
                            var newTask = { id: pl.id, status: 'aborted' };
                            dataService.putTask(newTask);
                        }
                    });
                }
    
                var aborted_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; });
    
                if(aborted_selected_cep4_pipelines.length > 0) {
                    var liContent = '<li><a href="#">Reschedule aborted/error CEP4 pipelines</a></li>'
                    var liElement = angular.element(liContent);
                    ulElement.append(liElement);
                    liElement.on('click', function() {
                        closeContextMenu();
                        for(var pl of aborted_selected_cep4_pipelines) {
                            var newTask = { id: pl.id, status: 'prescheduled' };
                            dataService.putTask(newTask);
                        }
                    });
                }
    
                var closeContextMenu = function(cme) {
                    contextmenuElement.remove();
    
                    //unbind document close event handlers
                    docElement.unbind('click', closeContextMenu);
                    docElement.unbind('contextmenu', closeContextMenu);
                };
    
                //click anywhere to remove the contextmenu
                docElement.bind('click', closeContextMenu);
                docElement.bind('contextmenu', closeContextMenu);
    
                //add contextmenu to body
                var body = $document.find('body');
                body.append(contextmenuElement);
    
                //prevent bubbling event upwards
                return false;
            }
    
            $element.bind('contextmenu', handleContextMenuEvent);
    
            $scope.$on('$destroy', function() {
                $element.unbind('contextmenu', handleContextMenuEvent);
            });
          }
        };
      }]);