diff --git a/background_scripts/batch_export_manager.php b/background_scripts/batch_export_manager.php index a092722593..007f7b62b3 100755 --- a/background_scripts/batch_export_manager.php +++ b/background_scripts/batch_export_manager.php @@ -13,6 +13,19 @@ ini_set('memory_limit', -1); try { + $lockFile = '/var/tmp/xdmod-batch-export.lock'; + $lockFileHandle = @fopen($lockFile, 'w'); + + if ($lockFileHandle === false) { + fwrite(STDERR, sprintf("Failed to open lock file \"%s\": %s\n", $lockFile, error_get_last())); + exit(1); + } + + if (!@flock($lockFileHandle, LOCK_EX | LOCK_NB)) { + fwrite(STDERR, "XDMoD Data Warehouse Batch Export not running due to another process holding the lock.\n"); + exit(1); + } + $help = false; $dryRun = false; $logLevel = -1; @@ -76,6 +89,9 @@ $batchProcessor->processRequests(); // NOTE: "process_end_time" is needed for the log summary. $logger->notice(['message' => 'batch_export_manager end', 'process_end_time' => date('Y-m-d H:i:s')]); + @flock($lockFileHandle, LOCK_UN); + @fclose($lockFileHandle); + @unlink($lockFile); exit; } catch (Exception $e) { // Write any unexpected exceptions directly to STDERR since they may not diff --git a/classes/DataWarehouse/Export/BatchProcessor.php b/classes/DataWarehouse/Export/BatchProcessor.php index a8fa4324fa..fd2dc700e2 100644 --- a/classes/DataWarehouse/Export/BatchProcessor.php +++ b/classes/DataWarehouse/Export/BatchProcessor.php @@ -93,8 +93,10 @@ public function setDryRun($dryRun) */ public function processRequests() { - $this->processSubmittedRequests(); + // Delete removed and expiring files before creating new files. + $this->processDeletedRequests(); $this->processExpiringRequests(); + $this->processSubmittedRequests(); } /** @@ -213,6 +215,25 @@ private function processExpiringRequest(array $request) } } + /** + * Process requests that have been deleted. + * + * If a request has been deleted then the associated data file needs to be + * removed from the file system. + */ + private function processDeletedRequests() + { + $this->logger->info('Processing deleted requests'); + $this->fileManager->removeDeletedRequests( + array_map( + function ($request) { + return $request['id']; + }, + $this->queryHandler->listAvailableRecords() + ) + ); + } + /** * Get the data set for the given request. * diff --git a/classes/DataWarehouse/Export/FileManager.php b/classes/DataWarehouse/Export/FileManager.php index a8017efbf2..603b1dd943 100644 --- a/classes/DataWarehouse/Export/FileManager.php +++ b/classes/DataWarehouse/Export/FileManager.php @@ -271,4 +271,33 @@ public function removeExportFile($id) throw new Exception(sprintf('Failed to delete "%s"', $zipFile)); } } + + /** + * Remove all data files corresponding to deleted requests. + * + * @param array $availableRequestIds Request IDs for "Available" export + * files. These correspond to data files that should not be deleted. + */ + public function removeDeletedRequests(array $availableRequestIds) + { + $availableFiles = array_map( + [$this, 'getExportDataFilePath'], + $availableRequestIds + ); + + foreach (glob($this->exportDir . '/*.zip') as $exportFile) { + if (!in_array($exportFile, $availableFiles)) { + $this->logger->info([ + 'message' => 'Removing export file', + 'zip_file' => $exportFile + ]); + if (!unlink($exportFile)) { + throw new Exception(sprintf( + 'Failed to delete "%s"', + $exportFile + )); + } + } + } + } } diff --git a/classes/DataWarehouse/Export/QueryHandler.php b/classes/DataWarehouse/Export/QueryHandler.php index 1f4dc3f32e..97d40ccda2 100644 --- a/classes/DataWarehouse/Export/QueryHandler.php +++ b/classes/DataWarehouse/Export/QueryHandler.php @@ -204,6 +204,18 @@ public function listSubmittedRecords() return $this->dbh->query($sql); } + /** + * Return details of all export requests presently in Available state. + * + * @return array + */ + public function listAvailableRecords() + { + $sql = 'SELECT id, user_id, realm, start_date, end_date, export_file_format, requested_datetime + FROM batch_export_requests ' . $this->whereAvailable . ' ORDER BY requested_datetime, id'; + return $this->dbh->query($sql); + } + /** * Return export requests in Available state that should expire. * diff --git a/classes/OpenXdmod/Migration/Version812To850/ConfigFilesMigration.php b/classes/OpenXdmod/Migration/Version812To850/ConfigFilesMigration.php index 4925e194d8..ee73653824 100644 --- a/classes/OpenXdmod/Migration/Version812To850/ConfigFilesMigration.php +++ b/classes/OpenXdmod/Migration/Version812To850/ConfigFilesMigration.php @@ -103,9 +103,14 @@ public function execute() 'data_warehouse_export_export_directory' => '/var/spool/xdmod/export', 'data_warehouse_export_retention_duration_days' => 30 ]); - + $data = parse_ini_file($this->portalSettingsPath, true); + $roadmapUrl = 'https://trello.com/embed/board?id=mdFESh6j'; + if('https://trello.com/b/mdFESh6j.html' !== $data['roadmap']['url']){ + $roadmapUrl = $data['roadmap']['url']; + } $this->writePortalSettingsFile(array_merge( ['features_user_dashboard' => $dashboard], + ['roadmap_url' => $roadmapUrl], $exportSettings )); } diff --git a/classes/Rest/Controllers/DashboardControllerProvider.php b/classes/Rest/Controllers/DashboardControllerProvider.php index e1137b156a..ea20201b94 100644 --- a/classes/Rest/Controllers/DashboardControllerProvider.php +++ b/classes/Rest/Controllers/DashboardControllerProvider.php @@ -71,7 +71,26 @@ private function getConfigVariables($user) } /** + * The individual dashboard components have a namespace prefix to simplify + * the implementation of the algorithm that determines which + * components to display. There are two sources of configuration data for + * the components. The roles configuration file and the user configuration + * (in the database). The user configuration only contains chart components. + * The user configuration is handled via the "Show in Summary tab" checkbox + * in the metric explorer. + * + * Non-chart components and the full-width components are defined in the roles + * configuration file and are not overrideable. + * + * Chart components are handled as follows: + * - All user charts with "show in summary tab" checked will be displayed + * - If a user chart has the same name as a chart in the role configuration + * then its settings will be used in place of the role chart. */ + const TOP_COMPONENT = 't.'; + const CHART_COMPONENT = 'c.'; + const NON_CHART_COMPONENT = 'p.'; + public function getComponents(Request $request, Application $app) { $user = $this->getUserFromRequest($request); @@ -94,20 +113,30 @@ public function getComponents(Request $request, Application $app) if (isset($presets['dashboard_components'])) { foreach($presets['dashboard_components'] as $component) { + + $componentType = self::NON_CHART_COMPONENT; + if (isset($component['region']) && $component['region'] === 'top') { - $chartLocation = 'FW' . $component['name']; + $componentType = self::TOP_COMPONENT; + $chartLocation = $componentType . $component['name']; $column = -1; } else { + if ($component['type'] === 'xdmod-dash-chart-cmp') { + $componentType = self::CHART_COMPONENT; + $component['config']['name'] = $component['name']; + $component['config']['chart']['featured'] = true; + } + $defaultLayout = null; if (isset($component['location']) && isset($component['location']['row']) && isset($component['location']['column'])) { $defaultLayout = array($component['location']['row'], $component['location']['column']); } - list($chartLocation, $column) = $layout->getLocation('PP' . $component['name'], $defaultLayout); + list($chartLocation, $column) = $layout->getLocation($componentType . $component['name'], $defaultLayout); } $dashboardComponents[$chartLocation] = array( - 'name' => 'PP' . $component['name'], + 'name' => $componentType . $component['name'], 'type' => $component['type'], 'config' => isset($component['config']) ? $component['config'] : array(), 'column' => $column @@ -115,26 +144,6 @@ public function getComponents(Request $request, Application $app) } } - $presetCharts = isset($presets['summary_charts']) ? $presets['summary_charts'] : $roleConfig['roles']['default']['summary_charts']; - - foreach ($presetCharts as $index => $presetChart) - { - $presetChart['featured'] = true; - $presetChart['aggregation_unit'] = 'Auto'; - $presetChart['timeframe_label'] = 'Previous month'; - - list($chartLocation, $column) = $layout->getLocation('PC' . $index); - $dashboardComponents[$chartLocation] = array( - 'name' => 'PC' . $index, - 'type' => 'xdmod-dash-chart-cmp', - 'config' => array( - 'name' => 'summary_' . $index, - 'chart' => $presetChart - ), - 'column' => $column - ); - } - if ($user->isPublicUser() === false) { $queryStore = new \UserStorage($user, 'queries_store'); @@ -152,13 +161,7 @@ public function getComponents(Request $request, Application $app) continue; } - $name = 'UC' . $query['name']; - - if (preg_match('/summary_(?P\S+)/', $query['name'], $matches) > 0) { - if ($layout->hasLayout('PC' . $matches['index'])) { - $name = 'PC' . $matches['index']; - } - } + $name = self::CHART_COMPONENT . $query['name']; list($chartLocation, $column) = $layout->getLocation($name); diff --git a/configuration/assets.json b/configuration/assets.json index c6bc13e52d..a7357c3d62 100644 --- a/configuration/assets.json +++ b/configuration/assets.json @@ -9,6 +9,7 @@ ], "css": [ "gui/css/JobComponent.css", + "gui/css/ChartComponent.css", "gui/css/ReportThumbnailsComponent.css" ] } diff --git a/configuration/etl/etl_data.d/xdb/report-templates.json b/configuration/etl/etl_data.d/xdb/report-templates.json index 32b7cb77ca..aa4eca8e98 100644 --- a/configuration/etl/etl_data.d/xdb/report-templates.json +++ b/configuration/etl/etl_data.d/xdb/report-templates.json @@ -1,6 +1,6 @@ [ ["id", "name", "description", "template", "title", "header", "footer", "format", "font", "schedule", "delivery", "charts_per_page", "use_submenu"], [1, "Quarterly Report - Center Director", "Quarterly Report - Center Director", "GenericReportTemplate", "Quarterly Report - Center Director", "", "", "Pdf", "Arial", "Quarterly", "E-mail", 1, 0], - [2, "Summary Tab Report", "Summary Tab Report", "GenericReportTemplate", "Summary Tab Report", "", "", "Pdf", "Arial", "Once", "E-mail", 1, 0], - [3, "Summary Tab Report", "Summary Tab Report", "GenericReportTemplate", "Summary Tab Report", "", "", "Pdf", "Arial", "Once", "E-mail", 1, 0] + [2, "Dashboard Tab Report", "Dashboard Tab Report", "GenericReportTemplate", "Dashboard Tab Report", "", "", "Pdf", "Arial", "Once", "E-mail", 1, 0], + [3, "Dashboard Tab Report", "Dashboard Tab Report", "GenericReportTemplate", "Dashboard Tab Report", "", "", "Pdf", "Arial", "Once", "E-mail", 1, 0] ] diff --git a/configuration/portal_settings.ini b/configuration/portal_settings.ini index 6e061d9017..84a302cc88 100644 --- a/configuration/portal_settings.ini +++ b/configuration/portal_settings.ini @@ -61,7 +61,7 @@ dw_desc_cache = "off" ; Settings that control the "Roadmap" action on the "About" tab. [roadmap] -url = "https://trello.com/b/mdFESh6j.html" +url = "https://trello.com/embed/board?id=mdFESh6j" header = "Located below is the XDMoD Development roadmap, organized by XDMoD release and powered by Trello.com. To view the full roadmap as well as vote and comment on features click any one of the elements on the roadmap. This will take you to the full roadmap on the Trello.com site in a new browser window (or tab). All users will be able to view the roadmap, however if you wish to vote or comment on a feature you will need to create a (free) Trello account if you do not already have one." [rest] diff --git a/configuration/roles.d/dashboard.json b/configuration/roles.d/dashboard.json index 0090b83034..26503a552d 100644 --- a/configuration/roles.d/dashboard.json +++ b/configuration/roles.d/dashboard.json @@ -67,7 +67,7 @@ } }, { - "name": "summary_0", + "name": "DashBoardChart_PiWallHoursByPerson", "type": "xdmod-dash-chart-cmp", "config": { "chart": { @@ -171,7 +171,7 @@ } }, { - "name": "summary_1", + "name": "DashBoardChart_PiWaitHoursByJobSize", "type": "xdmod-dash-chart-cmp", "config": { "chart": { @@ -223,7 +223,7 @@ "category": "Jobs", "color": "8BBC21", "combine_type": "side", - "display_type": "bar", + "display_type": "column", "enabled": true, "filters": { "data": [], @@ -254,17 +254,6 @@ }, "font_size": 2, "hide_tooltip": false, - "legend": { - "Std Err: Wait Hours: Per Job": { - "title": "Std Err" - }, - "Std Err: Wait Hours: Per Job {User = ${PERSON_NAME}}": { - "title": "Std Err" - }, - "Wait Hours: Per Job {User = ${PERSON_NAME}}": { - "title": "My Wait Hours: Per Job" - } - }, "legend_type": "top_center", "limit": 20, "share_y_axis": false, @@ -361,7 +350,7 @@ "category": "Jobs", "color": "8BBC21", "combine_type": "side", - "display_type": "bar", + "display_type": "column", "enabled": true, "filters": { "data": [], @@ -392,17 +381,6 @@ }, "font_size": 2, "hide_tooltip": false, - "legend": { - "Std Err: Wait Hours: Per Job": { - "title": "Std Err" - }, - "Std Err: Wait Hours: Per Job {User = ${PERSON_NAME}}": { - "title": "Std Err" - }, - "Wait Hours: Per Job {User = ${PERSON_NAME}}": { - "title": "My Wait Hours: Per Job" - } - }, "legend_type": "top_center", "limit": 20, "share_y_axis": false, @@ -420,7 +398,7 @@ "column": 1, "row": 0 }, - "name": "summary_0", + "name": "DashBoardChart_WaitHoursByJobSize", "type": "xdmod-dash-chart-cmp" }, { @@ -461,22 +439,6 @@ "total": 1 }, "font_size": 3, - "global_filters": { - "data": [ - { - "categories": "", - "checked": true, - "dimension_id": "resource", - "id": "resource=13", - "realms": [ - "Jobs" - ], - "value_id": "13", - "value_name": "ub hpc" - } - ], - "total": 1 - }, "hide_tooltip": false, "legend_type": "off", "limit": 10, @@ -501,7 +463,7 @@ "column": 0, "row": 1 }, - "name": "summary_1", + "name": "DashBoardChart_WaitHoursByQueue", "type": "xdmod-dash-chart-cmp" } ] diff --git a/configuration/roles.d/summary.json b/configuration/roles.d/summary.json deleted file mode 100644 index 9b347ff695..0000000000 --- a/configuration/roles.d/summary.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "+roles": { - "+cd": { - "summary_portlets": [ - { - "name": "Chart Thumbnails", - "type": "ReportThumbnailsPortlet", - "region": "top" - }, - { - "name": "Recents", - "type": "RecentChartsReportsPortlet", - "location": { - "row": 0, - "column": 0 - } - } - ] - }, - "+cs": { - "summary_portlets": [ - { - "name": "Chart Thumbnails", - "type": "ReportThumbnailsPortlet", - "region": "top" - }, - { - "name": "Recents", - "type": "RecentChartsReportsPortlet", - "location": { - "row": 0, - "column": 0 - } - } - ] - }, - "+pi": { - "summary_portlets": [ - { - "name": "Job Portlet", - "type": "JobPortlet", - "location": { - "row": 0, - "column": 0 - }, - "config": { - "timeframe": "30 day", - "multiuser": true - } - } - ] - }, - "+usr": { - "summary_portlets": [ - { - "name": "Job Portlet", - "type": "JobPortlet", - "location": { - "row": 0, - "column": 0 - }, - "config": { - "timeframe": "30 day" - } - } - ] - }, - "+usr": { - "summary_portlets": [ - { - "name": "Job Portlet", - "type": "JobPortlet", - "config": { - "timeframe": "30 day" - } - } - ] - }, - "+pi": { - "summary_portlets": [ - { - "name": "Job Portlet", - "type": "JobPortlet", - "config": { - "timeframe": "30 day", - "multiuser": true - } - } - ] - }, - "+pub": { - "summary_portlets": [ - { - "name": "Summary Statistics", - "type": "SummaryStatisticsPortlet", - "region": "top", - "config": { - "timeframe": "Last month" - } - } - ] - } - } -} diff --git a/docs/dashboard.md b/docs/dashboard.md index 0d203f37e3..7c3d1b8570 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -2,6 +2,17 @@ title: User Dashboard (Beta) --- +Known Issues +------------ + +* Edit In Metric Explorer for Dashboard Charts + +The "Edit In Metric Explorer" button on a dashboard chart will not load the +dashboard chart if there already exists a chart with an identical name saved in +the metric explorer. The workaround for this is to change the name of + the existing saved chart in the metric explorer so that it does not +match the dashboard chart name. + Developer Guide --------------- diff --git a/html/controllers/metric_explorer/get_dw_descripter.php b/html/controllers/metric_explorer/get_dw_descripter.php index 42ea125832..b225463012 100644 --- a/html/controllers/metric_explorer/get_dw_descripter.php +++ b/html/controllers/metric_explorer/get_dw_descripter.php @@ -45,11 +45,15 @@ foreach($query_descripter_realms as $query_descripter_realm => $query_descripter_groups) { + $category = DataWarehouse::getCategoryForRealm($query_descripter_realm); + if ($category === null) { + continue; + } $seenstats = array(); $realms[$query_descripter_realm] = array( 'text' => $query_descripter_realm, - 'category' => DataWarehouse::getCategoryForRealm($query_descripter_realm), + 'category' => $category, 'dimensions' => array(), 'metrics' => array(), ); diff --git a/html/gui/css/ChartComponent.css b/html/gui/css/ChartComponent.css new file mode 100644 index 0000000000..2651aed31a --- /dev/null +++ b/html/gui/css/ChartComponent.css @@ -0,0 +1,22 @@ +.dashboard-help-win { + font-size: 12px; + margin: 1em; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden +} + +.dashboard-help-win li { + padding-left: 1em +} + +.dashboard-help-win p { + margin-top: 4px; +} + +.dashboard-help-win img { + flex-shrink: 0; + min-width: 100%; + min-height: 100% +} diff --git a/html/gui/css/JobPortlet.css b/html/gui/css/JobPortlet.css deleted file mode 100644 index c68b8bf86c..0000000000 --- a/html/gui/css/JobPortlet.css +++ /dev/null @@ -1,17 +0,0 @@ -.job-portlet-grid td { - padding-top: 1px; -} - -div.circle { - width: 14px; - height: 14px; - border: solid 1px #555; - background-color: #eed; - box-shadow: 1px 1px rgba(0,0,0,0.6); - -moz-box-shadow: 1px 1px rgba(0,0,0,0.6); - -webkit-box-shadow: 1px 1px rgba(0,0,0,0.6); - -o-box-shadow: 1px 1px rgba(0,0,0,0.6); - border-radius: 14px; - display: inline-block; - margin-left: 5px; -} diff --git a/html/gui/css/ReportThumbnailsPortlet.css b/html/gui/css/ReportThumbnailsPortlet.css deleted file mode 100644 index 195cef61a2..0000000000 --- a/html/gui/css/ReportThumbnailsPortlet.css +++ /dev/null @@ -1,51 +0,0 @@ -.images-view .x-panel-body { - background: white; - font: 11px Arial, Helvetica, sans-serif; -} - -.images-view .thumb { - background: #dddddd; - padding: 3px; -} - -.images-view .thumb img { - height: 180px; - width: 278px; -} - -.images-view .thumb-wrap { - float: left; - margin: 4px; - margin-right: 0; - padding: 5px; -} - -.images-view .thumb-wrap span { - display: block; - overflow: hidden; - text-align: center; -} - -.images-view .x-view-over { - border: 1px solid #dddddd; - background: #efefef repeat-x left top; - padding: 4px; -} - -.images-view .x-view-selected { - background: #eff5fb no-repeat right bottom; - border: 1px solid #99bbe8; - padding: 4px; -} - -.images-view .x-view-selected .thumb { - background: transparent; -} - -.images-view .loading-indicator { - font-size: 11px; - background-repeat: no-repeat; - background-position: left; - padding-left: 20px; - margin: 10px; -} diff --git a/html/gui/images/help/chart-component.svg b/html/gui/images/help/chart-component.svg new file mode 100644 index 0000000000..8c97a659f5 --- /dev/null +++ b/html/gui/images/help/chart-component.svg @@ -0,0 +1 @@ + diff --git a/html/gui/images/help/chartsreports-component.svg b/html/gui/images/help/chartsreports-component.svg new file mode 100644 index 0000000000..cdbaf7613f --- /dev/null +++ b/html/gui/images/help/chartsreports-component.svg @@ -0,0 +1 @@ + diff --git a/html/gui/images/help/job-component.svg b/html/gui/images/help/job-component.svg new file mode 100644 index 0000000000..6cf6d67663 --- /dev/null +++ b/html/gui/images/help/job-component.svg @@ -0,0 +1 @@ + diff --git a/html/gui/images/help/job-multi-component.svg b/html/gui/images/help/job-multi-component.svg new file mode 100644 index 0000000000..7939853202 --- /dev/null +++ b/html/gui/images/help/job-multi-component.svg @@ -0,0 +1 @@ + diff --git a/html/gui/js/CCR.js b/html/gui/js/CCR.js index c606107a63..ffbe348ba7 100644 --- a/html/gui/js/CCR.js +++ b/html/gui/js/CCR.js @@ -1753,7 +1753,7 @@ CCR.xdmod.ui.presentFailureResponse = function (response, options) { // If a user-friendly message was given, add it to the displayed message. var outputMessage; if (options.wrapperMessage) { - outputMessage = options.wrapperMessage + " (" + responseMessage + ")"; + outputMessage = options.wrapperMessage + '
(' + responseMessage + ')'; } else { outputMessage = responseMessage; } @@ -1795,7 +1795,7 @@ CCR.xdmod.ui.getComboBox = function (data, fields, valueField, displayField, edi }; CCR.xdmod.ui.gridComboRenderer = function (combo) { return function (value) { - var idx = combo.store.find(combo.valueField, value); + var idx = combo.store.findExact(combo.valueField, value); var rec = combo.store.getAt(idx); if (!rec) { return combo.emptyText; diff --git a/html/gui/js/Portlet.js b/html/gui/js/Portlet.js index 8cc23dc13f..775fcafcd7 100644 --- a/html/gui/js/Portlet.js +++ b/html/gui/js/Portlet.js @@ -9,7 +9,10 @@ * layout: 'fit', * autoScroll: true, * title: 'example portlet', - * id: 'example_portlet', + * help: { + * html: 'The html for the help window', + * title: 'The title for the help window' + * }, * helpTourDetails: { * startAt: '#some-css-selector', * title: Title for help tips', @@ -33,9 +36,63 @@ * } */ CCR.xdmod.ui.Portlet = Ext.extend(Ext.ux.Portlet, { + collapsible: false, helpTour: null, helpTourDetails: {}, initComponent: function () { + if (!this.tools) { + this.tools = []; + } + + if (this.help) { + // Store a reference to the window to prevent multiple windows + // being created if the user clicks the help button multiple times. + var helpwin; + + this.tools.push({ + id: 'help', + qtip: 'Display help dialog', + handler: function (evt, tool, panel) { + if (!helpwin) { + var height = Math.min(CCR.xdmod.ui.Viewer.getViewer().getHeight(), 648); + + helpwin = new Ext.Window({ + layout: 'fit', + width: Math.round((4.0 * (height - 44)) / 3.0), + height: height, + title: 'Help for ' + panel.help.title, + items: { + html: panel.help.html + }, + listeners: { + close: function () { + helpwin = null; + } + }, + bbar: { + items: [ + { + text: 'More Information', + handler: function () { + window.open('user_manual.php?t=Dashboard'); + } + }, + '->', + { + text: 'Close', + handler: function () { + helpwin.close(); + } + } + ] + } + }); + } + helpwin.show(); + } + }); + } + if (this.helpTourDetails.tips !== undefined && this.helpTourDetails.tips.length > 0) { this.helpTourDetails.tips.forEach(function (value, index, arr) { arr[index].target = (value.target.slice(0, 1) !== '/') ? '#' + this.id + ' ' + value.target : value.target.slice(1, value.target.length); @@ -46,10 +103,6 @@ CCR.xdmod.ui.Portlet = Ext.extend(Ext.ux.Portlet, { items: this.helpTourDetails.tips }); - if (this.tools === undefined) { - this.tools = []; - } - this.tools.push({ id: 'maximize', qtip: 'View Component Help Tour', diff --git a/html/gui/js/modules/Usage.js b/html/gui/js/modules/Usage.js index 4636e239c1..8c35b61c57 100644 --- a/html/gui/js/modules/Usage.js +++ b/html/gui/js/modules/Usage.js @@ -1614,16 +1614,29 @@ Ext.extend(XDMoD.Module.Usage, XDMoD.PortalModule, { handleDataException = function (response, exceptionType) { var viewer = CCR.xdmod.ui.Viewer.getViewer(); + if (viewer && viewer.el) { + viewer.el.unmask(); + } + + var options = {}; + + view.tpl = new Ext.XTemplate(['', '
An error has occurred.
', '
']); + view.store.loadData({ + totalCount: 1, + success: true, + message: 'Error', + data: [ + { + } + ] + }); + view.refresh(); if (exceptionType === 'response') { var data = CCR.safelyDecodeJSONResponse(response) || {}; var errorCode = data.code; if (errorCode === XDMoD.Error.QueryUnavailableTimeAggregationUnit) { - if (viewer && viewer.el) { - viewer.el.unmask(); - } - var durationToolbar = self.getDurationSelector(); durationToolbar.setAggregationUnit('Auto'); durationToolbar.onHandle(); @@ -1644,40 +1657,12 @@ Ext.extend(XDMoD.Module.Usage, XDMoD.PortalModule, { errorMessage += '
' + errorMessageExtraData; } } - - Ext.MessageBox.alert( - 'Usage', - errorMessage - ); - return; - } - var extraInfo = JSON.parse(data.message); - var groupDescription = extraInfo.description; - if (extraInfo.statistic !== '') { - groupDescription += ' by ' + extraInfo.statistic; + options.title = 'Usage'; + options.wrapperMessage = errorMessage; } - viewer.el.unmask(); - view.tpl = new Ext.XTemplate(['', '
{group_description}
{description}
', '
']); - view.store.loadData({ - totalCount: 1, - success: true, - message: 'Error', - data: [ - { - group_description: groupDescription, - description: extraInfo.instructions - } - ] - }); - view.refresh(); - return; } - CCR.xdmod.ui.presentFailureResponse(response); - - if (viewer && viewer.el) { - viewer.el.unmask(); - } + CCR.xdmod.ui.presentFailureResponse(response, options); }; // --------------------------------------------------------- @@ -1788,7 +1773,6 @@ Ext.extend(XDMoD.Module.Usage, XDMoD.PortalModule, { store: chartStore, autoScroll: true, tpl: largeChartTemplate - }); //view // --------------------------------------------------------- @@ -2675,6 +2659,13 @@ Ext.extend(XDMoD.Module.Usage, XDMoD.PortalModule, { 'Chart: ' + chartStore.getAt(0).get('title') + ', Params: ' + chartStore.getAt(0).get('params_title') ); + var serverChartSettings = chartStore.getAt(0).get('chart_settings').replace(/`/g, '"'); + if (serverChartSettings === '') { + serverChartSettings = n.attributes.defaultChartSettings; + } + n.attributes.chartSettings = serverChartSettings; + chartToolbar.fromJSON(n.attributes.chartSettings); + self.setImageExport(true); if (public_user != true) diff --git a/html/gui/js/modules/dashboard/ChartComponent.js b/html/gui/js/modules/dashboard/ChartComponent.js index 543b48e999..a060a6cdb4 100644 --- a/html/gui/js/modules/dashboard/ChartComponent.js +++ b/html/gui/js/modules/dashboard/ChartComponent.js @@ -5,7 +5,7 @@ Ext.namespace('XDMoD.Module.Dashboard'); -XDMoD.Module.Dashboard.ChartComponent = Ext.extend(Ext.ux.Portlet, { +XDMoD.Module.Dashboard.ChartComponent = Ext.extend(CCR.xdmod.ui.Portlet, { layout: 'fit', tools: [{ @@ -16,13 +16,16 @@ XDMoD.Module.Dashboard.ChartComponent = Ext.extend(Ext.ux.Portlet, { handler: function (event, toolEl, panel) { XDMoD.Module.MetricExplorer.setConfig(panel.config.chart, panel.config.name, false); } - }, { - id: 'help' }], initComponent: function () { var self = this; + this.help = { + html: '', + title: 'Chart' + }; + this.title = Ext.util.Format.ellipsis(this.config.chart.title, 60, true); // Sync date ranges @@ -52,7 +55,7 @@ XDMoD.Module.Dashboard.ChartComponent = Ext.extend(Ext.ux.Portlet, { var dimension; for (dimension in dimensions) { if (dimensions.hasOwnProperty(dimension)) { - dims += '
  • ' + dimension + ': ' + dimensions[dimension] + '
  • '; + dims += '
  • ' + dimension + ': ' + dimensions[dimension] + '
  • '; } } var metrics = store.getAt(0).get('metrics'); @@ -61,13 +64,14 @@ XDMoD.Module.Dashboard.ChartComponent = Ext.extend(Ext.ux.Portlet, { var metric; for (metric in metrics) { if (metrics.hasOwnProperty(metric)) { - mets += '
  • ' + metric + ': ' + metrics[metric] + '
  • '; + mets += '
  • ' + metric + ': ' + metrics[metric] + '
  • '; } } - var help = this.chartCmp.getTool('help'); - if (help && help.dom) { - help.dom.qtip = '
    '; - } + self.help.html = '
    ' + + '

    A description of the data in the chart is shown below.

    ' + + '

    Dimensions:

    ' + + '

    Metrics:

    ' + + '
    '; }, exception: function (thisProxy, type, action, options, response) { if (type === 'response') { diff --git a/html/gui/js/modules/dashboard/JobComponent.js b/html/gui/js/modules/dashboard/JobComponent.js index 161df61c3c..1e3baf7411 100644 --- a/html/gui/js/modules/dashboard/JobComponent.js +++ b/html/gui/js/modules/dashboard/JobComponent.js @@ -6,14 +6,10 @@ Ext.namespace('XDMoD.Module.Dashboard'); -XDMoD.Module.Dashboard.JobComponent = Ext.extend(Ext.ux.Portlet, { +XDMoD.Module.Dashboard.JobComponent = Ext.extend(CCR.xdmod.ui.Portlet, { layout: 'fit', - collapsible: false, title: 'Jobs', - tools: [{ - id: 'help' - }], initComponent: function () { var page_size = 9; @@ -22,6 +18,16 @@ XDMoD.Module.Dashboard.JobComponent = Ext.extend(Ext.ux.Portlet, { return moment(value * 1000).format('Y-MM-DD HH:mm:ss z'); }; + this.help = { + title: 'Jobs' + }; + + if (this.config.multiuser) { + this.help.html = ''; + } else { + this.help.html = ''; + } + var jobEfficiency = function (value, metadata, record) { var getDataColor = function (data) { var color = 'gray'; @@ -71,13 +77,6 @@ XDMoD.Module.Dashboard.JobComponent = Ext.extend(Ext.ux.Portlet, { this.title += ' - ' + date.start.format('Y-m-d') + ' to ' + date.end.format('Y-m-d'); - if (!this.config.multiuser) { - this.tools[0].qtip = 'This panel shows a list of your HPC jobs for which there is data in XDMoD. Click on a row to view the detailed information about a job.'; - page_size = 10; - } else { - this.tools[0].qtip = 'This panel shows a list of the HPC jobs that ran under your account for which there is data in XDMoD. Click on a row to view the detailed information about a job.'; - } - // The default search parameters are set to all jobs - this // will result in all of the jobs that the user has permission to // view. diff --git a/html/gui/js/modules/dashboard/ReportThumbnailsComponent.js b/html/gui/js/modules/dashboard/ReportThumbnailsComponent.js index e1995ed9d5..a6104ba889 100644 --- a/html/gui/js/modules/dashboard/ReportThumbnailsComponent.js +++ b/html/gui/js/modules/dashboard/ReportThumbnailsComponent.js @@ -99,6 +99,7 @@ XDMoD.Module.Dashboard.ReportThumbnailsComponent = Ext.extend(Ext.Panel, { listeners: { click: { fn: function (dataView, index, node, e) { + var win; // Window to display the chart this.tmpHpc = new CCR.xdmod.ui.HighChartPanel({ chartOptions: { chart: { @@ -137,7 +138,12 @@ XDMoD.Module.Dashboard.ReportThumbnailsComponent = Ext.extend(Ext.Panel, { proxy: new Ext.data.HttpProxy({ method: 'POST', url: 'controllers/metric_explorer.php' - }) + }), + listeners: { + load: function () { + win.el.unmask(); + } + } }) @@ -175,7 +181,7 @@ XDMoD.Module.Dashboard.ReportThumbnailsComponent = Ext.extend(Ext.Panel, { this.tmpHpc.store.setBaseParam('operation', 'get_data'); - var win = new Ext.Window({ + win = new Ext.Window({ layout: 'fit', width: 800, height: 600, @@ -187,7 +193,7 @@ XDMoD.Module.Dashboard.ReportThumbnailsComponent = Ext.extend(Ext.Panel, { text: 'Open in Metric Explorer', handler: function () { win.destroy(); - XDMoD.Module.MetricExplorer.setConfig(config, config.title, false); + XDMoD.Module.MetricExplorer.setConfig(config, config.title, true); } }, { text: 'Close', @@ -201,6 +207,7 @@ XDMoD.Module.Dashboard.ReportThumbnailsComponent = Ext.extend(Ext.Panel, { if (viewer.el) { viewer.el.mask(); } + win.el.mask('Loading...'); }, destroy: function () { var viewer = CCR.xdmod.ui.Viewer.getViewer(); diff --git a/html/gui/js/modules/dashboard/SavedChartsReportsComponent.js b/html/gui/js/modules/dashboard/SavedChartsReportsComponent.js index 1d66f3ef6e..b6ad516d15 100644 --- a/html/gui/js/modules/dashboard/SavedChartsReportsComponent.js +++ b/html/gui/js/modules/dashboard/SavedChartsReportsComponent.js @@ -10,6 +10,11 @@ XDMoD.Module.Dashboard.SavedChartsReportsComponent = Ext.extend(CCR.xdmod.ui.Por initComponent: function () { var aspectRatio = 11 / 17; + this.help = { + title: this.title, + html: '' + }; + this.chartReportStore = new Ext.data.JsonStore({ // store configs autoDestroy: true, @@ -42,7 +47,7 @@ XDMoD.Module.Dashboard.SavedChartsReportsComponent = Ext.extend(CCR.xdmod.ui.Por trigger2Class: 'x-form-search-trigger', hideTrigger1: true, enableKeyEvents: true, - emptyText: 'Search', + emptyText: 'Filter by name ...', store: this.chartReportStore, onTrigger1Click: function () { this.store.clearFilter(); @@ -106,6 +111,7 @@ XDMoD.Module.Dashboard.SavedChartsReportsComponent = Ext.extend(CCR.xdmod.ui.Por text: 'Refresh', scope: this, handler: function () { + searchField.onTrigger1Click(); this.chartReportStore.reload(); } } @@ -129,13 +135,7 @@ XDMoD.Module.Dashboard.SavedChartsReportsComponent = Ext.extend(CCR.xdmod.ui.Por this.height = this.width * aspectRatio; this.items = [this.chartReportGrid]; - this.tools = [ - { - id: 'help', - qtip: 'Porlet shows a list of saved charts and reports.', - qwidth: 60 - } - ]; + XDMoD.Module.Dashboard.SavedChartsReportsComponent.superclass.initComponent.apply(this, arguments); } }); diff --git a/html/gui/js/modules/metric_explorer/MetricExplorer.js b/html/gui/js/modules/metric_explorer/MetricExplorer.js index da41f5535f..79aa83f593 100644 --- a/html/gui/js/modules/metric_explorer/MetricExplorer.js +++ b/html/gui/js/modules/metric_explorer/MetricExplorer.js @@ -6359,7 +6359,7 @@ Ext.extend(XDMoD.Module.MetricExplorer, XDMoD.PortalModule, { this.updateRawDataWindowVisibility(); if (location.hash.split('config=')[1]) { var config = JSON.parse(window.atob(location.hash.split('config=')[1])); - XDMoD.Module.MetricExplorer.setConfig(config, config.title, false); + XDMoD.Module.MetricExplorer.setConfig(config, config.title, true); } }, // activate diff --git a/html/gui/js/modules/summary/JobPortlet.js b/html/gui/js/modules/summary/JobPortlet.js deleted file mode 100644 index 2afb7aab6f..0000000000 --- a/html/gui/js/modules/summary/JobPortlet.js +++ /dev/null @@ -1,271 +0,0 @@ -/* global moment */ -/** - * XDMoD.Modules.SummaryPortlets.JobPortlet - * - */ - -Ext.namespace('XDMoD.Modules.SummaryPortlets'); - -XDMoD.Modules.SummaryPortlets.JobPortlet = Ext.extend(Ext.ux.Portlet, { - - layout: 'fit', - collapsible: false, - title: 'Jobs', - tools: [{ - id: 'help' - }], - - initComponent: function () { - var page_size = 9; - - var formatDateWithTimezone = function (value) { - return moment(value * 1000).format('Y-MM-DD HH:mm:ss z'); - }; - - var jobEfficiency = function (value, metadata, record) { - var getDataColor = function (data) { - var color = 'gray'; - var steps = [{ - value: 0.25, - color: '#FF0000' - }, { - value: 0.50, - color: '#FFB336' - }, { - value: 0.75, - color: '#DDDF00' - }, { - value: 1, - color: '#50B432' - }]; - - var i; - var step; - for (i = 0; i < steps.length; i++) { - step = steps[i]; - if (data <= step.value) { - color = step.color; - break; - } - } - return color; - }; - - if (record.data.cpu_user < 0) { - return 'N/A'; - } - - metadata.attr = 'ext:qtip="CPU Usage ' + (record.data.cpu_user * 100.0).toFixed(1) + '%"'; - - return String.format('
    ', getDataColor(record.data.cpu_user)); - }; - - // Sync date ranges - var dateRanges = CCR.xdmod.ui.DurationToolbar.getDateRanges(); - - var timeframe = this.config.timeframe ? this.config.timeframe : '30 day'; - - var date = dateRanges.find(function (element) { - return element.text === timeframe; - }, this); - - this.title += ' - ' + date.start.format('Y-m-d') + ' to ' + date.end.format('Y-m-d'); - - if (!this.config.multiuser) { - this.tools[0].qtip = 'This panel shows a list of your HPC jobs for which there is data in XDMoD. Click on a row to view the detailed information about a job.'; - page_size = 10; - } else { - this.tools[0].qtip = 'This panel shows a list of the HPC jobs that ran under your account for which there is data in XDMoD. Click on a row to view the detailed information about a job.'; - } - - // The default search parameters are set to all jobs - this - // will result in all of the jobs that the user has permission to - // view. - var defaultParams = {}; - - var jobStore = new Ext.data.JsonStore({ - restful: true, - url: XDMoD.REST.url + '/warehouse/search/jobs', - root: 'results', - autoLoad: true, - totalProperty: 'totalCount', - baseParams: { - start_date: date.start.format('Y-m-d'), - end_date: date.end.format('Y-m-d'), - realm: CCR.xdmod.ui.rawDataAllowedRealms[0], - limit: page_size, - start: 0, - verbose: true, - params: JSON.stringify(defaultParams) - }, - fields: [ - { name: 'dtype', mapping: 'dtype', type: 'string' }, - { name: 'resource', mapping: 'resource', type: 'string' }, - { name: 'name', mapping: 'name', type: 'string' }, - { name: 'jobid', mapping: 'jobid', type: 'int' }, - { name: 'local_job_id', mapping: 'local_job_id', type: 'int' }, - { name: 'text', mapping: 'text', type: 'string' }, - 'cpu_user', - 'start_time_ts', - 'end_time_ts' - ] - }); - - /* Set new search parameters for the job store and reset the - * paging to the beginning. Note that it is necessary to modify - * the baseParams because the paging toolbar is used. */ - var resetStore = function (newParams) { - jobStore.setBaseParam('params', JSON.stringify(newParams)); - jobStore.load({ - params: { - start: 0, - limit: page_size - } - }); - }; - - var columns = [{ - header: 'Job Identifier', - width: 140, - tooltip: 'The job identifier includes the resource that ran the job and the id provided by the resource manager.', - dataIndex: 'text' - }, { - header: 'Start', - renderer: formatDateWithTimezone, - tooltip: 'The start time of the job', - width: 115, - fixed: true, - dataIndex: 'start_time_ts' - }, { - header: 'End', - renderer: formatDateWithTimezone, - tooltip: 'The end time of the job', - width: 115, - fixed: true, - dataIndex: 'end_time_ts' - }, { - header: 'CPU', - renderer: jobEfficiency, - tooltip: 'The average CPU usage for the job. The text NA indicates that the metric is unavailable.', - width: 40, - fixed: true, - dataIndex: 'cpu_user' - }]; - - if (this.config.multiuser) { - columns.splice(0, 0, { - header: 'Person', - width: 90, - sortable: true, - dataIndex: 'name' - }); - } - - var gridpanel = { - xtype: 'grid', - frame: true, - store: jobStore, - enableHdMenu: false, - loadMask: true, - stripeRows: true, - cls: 'job-portlet-grid', - colModel: new Ext.grid.ColumnModel({ - defaults: { - sortable: true - }, - columns: columns - }), - viewConfig: { - emptyText: 'No Job Records found for the specified time range', - forceFit: true - }, - bbar: new Ext.PagingToolbar({ - store: jobStore, - displayInfo: true, - pageSize: page_size, - prependButtons: true - }), - sm: new Ext.grid.RowSelectionModel({ - singleSelect: true - }), - listeners: { - rowclick: function (panel, rowIndex) { - var store = panel.getStore(); - var info = store.getAt(rowIndex); - var params = { - action: 'show', - realm: store.baseParams.realm, - jobref: info.data[info.data.dtype] - }; - Ext.History.add('job_viewer?' + Ext.urlEncode(params)); - } - } - }; - - if (this.config.multiuser) { - gridpanel.tbar = { - items: [ - 'Filter: ', - ' ', - new Ext.form.ClearableComboBox({ - emptyText: 'Filter by Person...', - triggerAction: 'all', - selectOnFocus: true, - displayField: 'long_name', - valueField: 'id', - typeAhead: true, - mode: 'local', - forceSelection: true, - enableKeyEvents: true, - store: new Ext.data.JsonStore({ - url: XDMoD.REST.url + '/warehouse/dimensions/person', - restful: true, - autoLoad: true, - baseParams: { - realm: CCR.xdmod.ui.rawDataAllowedRealms[0] - }, - root: 'results', - fields: [ - { name: 'id', type: 'string' }, - { name: 'name', type: 'string' }, - { name: 'short_name', type: 'string' }, - { name: 'long_name', type: 'string' } - ], - listeners: { - exception: function (proxy, type, action, exception, response) { - switch (response.status) { - case 403: - case 500: - var details = Ext.decode(response.responseText); - Ext.Msg.alert('Error ' + response.status + ' ' + response.statusText, details.message); - break; - case 401: - // Do nothing - break; - default: - Ext.Msg.alert(response.status + ' ' + response.statusText, response.responseText); - } - } - } - }), - listeners: { - select: function (combo, record) { - resetStore({ person: [record.id] }); - }, - reset: function () { - resetStore(defaultParams); - } - } - }) - ] - }; - } - this.items = [gridpanel]; - - this.height = (this.width * 11.0) / 17.0; - - XDMoD.Modules.SummaryPortlets.JobPortlet.superclass.initComponent.apply(this, arguments); - } -}); - -Ext.reg('JobPortlet', XDMoD.Modules.SummaryPortlets.JobPortlet); diff --git a/html/gui/js/modules/summary/ReportThumbnailsPortlet.js b/html/gui/js/modules/summary/ReportThumbnailsPortlet.js deleted file mode 100644 index bb778eb856..0000000000 --- a/html/gui/js/modules/summary/ReportThumbnailsPortlet.js +++ /dev/null @@ -1,458 +0,0 @@ -/** - * XDMoD.Modules.SummaryPortlets.ReportThumbnailsPortlet - * - */ - -Ext.namespace('XDMoD.Modules.SummaryPortlets'); - -XDMoD.Modules.SummaryPortlets.ReportThumbnailsPortlet = Ext.extend(Ext.Panel, { - layout: 'fit', - header: false, - cls: 'images-view', - /** - * - */ - initComponent: function () { - var self = this; - - function filterRange(arr, label) { - var dateRange = {}; - for (var i = 0; i < arr.length; i++) { - if (arr[i].text === label) { - dateRange = { - start_date: arr[i].start.format('Y-m-d'), - end_date: arr[i].end.format('Y-m-d') - }; - } - } - return dateRange; - } - var ranges = CCR.xdmod.ui.DurationToolbar.getDateRanges(); - var timeframe_label = this.config.timeframe; - this.timeframe = filterRange(ranges, timeframe_label); - if (Object.keys(this.timeframe).length === 0) { - this.timeframe.start_date = null; - this.timeframe.end_date = null; - timeframe_label = 'Report default'; - } - - this.store = new Ext.data.JsonStore({ - url: XDMoD.REST.url + '/summary/rolereport', - root: 'data.queue', - fields: [ - 'chart_title', - { - name: 'thumbnail_link', - convert: function (v, rec) { - var params = {}; - var v_split = v.split('/report_image_renderer.php?')[1].split('&'); - for (var index = 0; index < v_split.length; index++) { - var tmpk = v_split[index].split('=')[0]; - var tmpv = v_split[index].split('=')[1]; - params[tmpk] = tmpv; - } - var value; - if (!(self.timeframe.start_date === null && self.timeframe.end_date === null)) { - value = '/report_image_renderer.php?type=cached&ref=' + params.ref; - value = value + '&start=' + self.timeframe.start_date + '&end=' + self.timeframe.end_date + '&token='; - } else { - value = '/report_image_renderer.php?type=report&ref=' + params.ref; - value += '&token='; - } - return value; - } - } - ] - }); - this.store.load(); - - var tpl = new Ext.XTemplate( - '', - '
    ', - '{shortName}', - '
    ', - '
    ', - '
    ', - '
    ' - ); - - this.panel = new Ext.DataView({ - store: this.store, - tpl: tpl, - autoHeight: true, - multiSelect: false, - overClass: 'x-view-over', - itemSelector: 'div.thumb-wrap', - emptyText: 'No images to display', - prepareData: function (data) { - data.shortName = Ext.util.Format.ellipsis(data.chart_title, 50); - var params = {}; - var v_split = data.thumbnail_link.split('/report_image_renderer.php?')[1].split('&'); - for (var index = 0; index < v_split.length; index++) { - var tmpk = v_split[index].split('=')[0]; - var tmpv = v_split[index].split('=')[1]; - params[tmpk] = tmpv; - } - data.report_id = params.ref.split(';')[0]; - return data; - }, - listeners: { - click: { - fn: function (dataView, index, node, e) { - this.tmpHpc = new CCR.xdmod.ui.HighChartPanel({ - chartOptions: { - chart: { - animation: this.public_user === true - }, - plotOptions: { - series: { - animation: this.public_user === true - } - } - }, - store: new CCR.xdmod.CustomJsonStore({ - autoDestroy: true, - root: 'data', - autoLoad: true, - totalProperty: 'totalCount', - successProperty: 'success', - messageProperty: 'message', - - fields: [ - 'chart', - 'credits', - 'title', - 'subtitle', - 'xAxis', - 'yAxis', - 'tooltip', - 'legend', - 'series', - 'dimensions', - 'metrics', - 'plotOptions', - 'reportGeneratorMeta' - ], - - proxy: new Ext.data.HttpProxy({ - method: 'POST', - url: 'controllers/metric_explorer.php' - }) - - }) - - }); // hcp - - var config = JSON.parse(JSON.stringify(dataView.store.data.items[index].json.chart_id)); - this.tmpHpc.store.removeAll(); - for (var key in config) { - if (key === 'data_series') { - this.tmpHpc.store.setBaseParam(key, Ext.util.JSON.encode(config[key])); - var data_series = {}; - data_series.data = config[key]; - data_series.total = config[key].length; - config.data_series = data_series; - } else if (key === 'global_filters') { - this.tmpHpc.store.setBaseParam(key, Ext.util.JSON.encode(config[key])); - } else { - this.tmpHpc.store.setBaseParam(key, config[key]); - } - } - if (!(self.timeframe.start_date === null && self.timeframe.end_date === null)) { - config.start_date = self.timeframe.start_date; - config.end_date = self.timeframe.end_date; - config.timeframe_label = 'User Defined'; - this.tmpHpc.store.setBaseParam('start_date', self.timeframe.start_date); - this.tmpHpc.store.setBaseParam('end_date', self.timeframe.end_date); - this.tmpHpc.store.setBaseParam('timeframe_label', 'User Defined'); - } else { - var timeframe = filterRange(ranges, config.timeframe_label); - config.start_date = timeframe.start_date; - config.end_date = timeframe.end_date; - this.tmpHpc.store.setBaseParam('start_date', timeframe.start_date); - this.tmpHpc.store.setBaseParam('end_date', timeframe.end_date); - } - - this.tmpHpc.store.setBaseParam('operation', 'get_data'); - - var win = new Ext.Window({ - layout: 'fit', - width: 800, - height: 600, - closeAction: 'destroy', - plain: true, - title: dataView.store.data.items[index].json.chart_title, - items: [this.tmpHpc], - buttons: [{ - text: 'Open in Metric Explorer', - handler: function () { - win.destroy(); - XDMoD.Module.MetricExplorer.setConfig(config, config.title, false); - } - }, { - text: 'Close', - handler: function () { - win.destroy(); - } - }], - listeners: { - show: function () { - var viewer = CCR.xdmod.ui.Viewer.getViewer(); - if (viewer.el) { - viewer.el.mask(); - } - }, - destroy: function () { - var viewer = CCR.xdmod.ui.Viewer.getViewer(); - viewer.el.unmask(); - } - } - }); - win.show(); - } - } - } - }); - this.items = [this.panel]; - this.tools = [ - { - id: 'help', - qtip: [ - '' - ].join(' '), - qwidth: 60 - } - ]; - this.tbar = { - items: [ - { - xtype: 'tbtext', - text: 'Time Range' - }, - { - xtype: 'button', - text: timeframe_label, - iconCls: 'custom_date', - menu: [{ - text: '30 day', - checked: timeframe_label === '30 day', - group: 'timeframe', - listeners: { - click: function (comp) { - var today = new Date(); - var lastMonth = today.add(Date.DAY, -30); - var start = lastMonth; - var end = today; - this.ownerCt.ownerCt.ownerCt.ownerCt.fireEvent('timeframe_change', start, end); - this.ownerCt.ownerCt.ownerCt.items.items[1].setText('30 day'); - this.ownerCt.ownerCt.ownerCt.items.items[2].setText('' + self.timeframe.start_date + ' - ' + self.timeframe.end_date + ''); - } - } - }, - { - text: 'Previous year', - checked: timeframe_label === 'Previous year', - group: 'timeframe', - listeners: { - click: function () { - var today = new Date(); - var oneYearAgoStart = new Date(today.getFullYear() - 1, 0, 1); - var oneYearAgoEnd = new Date(today.getFullYear() - 1, 11, 31); - var start = oneYearAgoStart; - var end = oneYearAgoEnd; - this.ownerCt.ownerCt.ownerCt.ownerCt.fireEvent('timeframe_change', start, end); - this.ownerCt.ownerCt.ownerCt.items.items[1].setText('Previous year'); - this.ownerCt.ownerCt.ownerCt.items.items[2].setText('' + self.timeframe.start_date + ' - ' + self.timeframe.end_date + ''); - } - } - }, - { - text: '5 year', - checked: timeframe_label === '5 year', - group: 'timeframe', - listeners: { - click: function () { - var today = new Date(); - var last5Year = today.add(Date.YEAR, -5); - var start = last5Year; - var end = today; - this.ownerCt.ownerCt.ownerCt.ownerCt.fireEvent('timeframe_change', start, end); - this.ownerCt.ownerCt.ownerCt.items.items[1].setText('5 year'); - this.ownerCt.ownerCt.ownerCt.items.items[2].setText('' + self.timeframe.start_date + ' - ' + self.timeframe.end_date + ''); - } - } - }, - { - text: 'Report default', - checked: timeframe_label === 'Report default', - group: 'timeframe', - listeners: { - click: function (comp) { - this.ownerCt.ownerCt.ownerCt.ownerCt.fireEvent('timeframe_change'); - this.ownerCt.ownerCt.ownerCt.items.items[1].setText('Report default'); - this.ownerCt.ownerCt.ownerCt.items.items[2].setText(''); - } - } - }] - }, - { - xtype: 'tbtext', - text: (self.timeframe.start_date !== null && self.timeframe.end_date !== null ? '' + self.timeframe.start_date + ' - ' + self.timeframe.end_date + '' : '') - }, - '->', - { - text: 'Download Report', - icon: 'gui/images/report_generator/pdf_icon.png', - cls: 'x-btn-text-icon', - listeners: { - click: function () { - var viewer = CCR.xdmod.ui.Viewer.getViewer(); - viewer.el.mask( - '
    Preparing report for download
    ' + - '
    ' + - '
    Please Wait
    ' - ); - var report_id = this.ownerCt.ownerCt.store.data.items[0].data.report_id; - var start_date = this.ownerCt.ownerCt.timeframe.start_date; - var end_date = this.ownerCt.ownerCt.timeframe.end_date; - var format = 'pdf'; - var conn = new Ext.data.Connection({ - // allow for generous 'execution time' so that lengthy - // reports can be compiled (10 min.) - timeout: 600000 - }); - conn.request({ - url: 'controllers/report_builder.php', - params: { - operation: 'send_report', - report_id: report_id, - build_only: true, - export_format: format, - start_date: start_date, - end_date: end_date - }, - method: 'POST', - callback: function (options, success, response) { - if (success) { - var responseData = CCR.safelyDecodeJSONResponse(response); - var successResponse = CCR.checkDecodedJSONResponseSuccess(responseData); - if (successResponse) { - var location = 'controllers/report_builder.php/' + - responseData.report_name + - '?operation=download_report&report_loc=' + - responseData.report_loc + '&format=' + format; - - var w = new Ext.Window({ - title: 'Report Built', - width: 220, - height: 120, - resizable: false, - closeAction: 'destroy', - layout: 'border', - cls: 'wnd_report_built', - - listeners: { - show: function () { - if (viewer.el) { - viewer.el.mask(); - } - }, - destroy: function () { - viewer.el.unmask(); - } - }, - - items: [ - new Ext.Panel({ - region: 'west', - width: 70, - html: '', - baseCls: 'x-plain' - }), - new Ext.Panel({ - region: 'center', - width: 150, - layout: 'border', - margins: '5 5 5 5', - items: [ - new Ext.Panel({ - region: 'center', - html: 'Your report has been built and can now be viewed.', - baseCls: 'x-plain' - }), - new Ext.Button({ - region: 'south', - text: 'View Report', - handler: function () { - XDMoD.TrackEvent( - 'Report Generator', - 'Clicked on View Report button in Report Built window' - ); - window.open(location); - } - }) - ] - }) - ] - }); - w.show(); - } - } - } - }); - } - } - } - ] - }; - XDMoD.Modules.SummaryPortlets.ReportThumbnailsPortlet.superclass.initComponent.apply(this, arguments); - }, - listeners: { - timeframe_change: function (start_date, end_date) { - if (start_date !== undefined && end_date !== undefined) { - this.timeframe.start_date = start_date.format('Y-m-d'); - this.timeframe.end_date = end_date.format('Y-m-d'); - } else { - this.timeframe.start_date = null; - this.timeframe.end_date = null; - } - this.store.load(); - } - } -}); - - -/** - * The Ext.reg call is used to register an xtype for this class so it - * can be dynamically instantiated - */ -Ext.reg('ReportThumbnailsPortlet', XDMoD.Modules.SummaryPortlets.ReportThumbnailsPortlet); diff --git a/html/gui/js/modules/summary/SavedChartsReportsPortlet.js b/html/gui/js/modules/summary/SavedChartsReportsPortlet.js deleted file mode 100644 index 4586d9b333..0000000000 --- a/html/gui/js/modules/summary/SavedChartsReportsPortlet.js +++ /dev/null @@ -1,147 +0,0 @@ -Ext.namespace('XDMoD.Modules.SummaryPortlets'); - -XDMoD.Modules.SummaryPortlets.SavedChartsReportsPortlet = Ext.extend(CCR.xdmod.ui.Portlet, { - - layout: 'fit', - autoScroll: true, - title: 'Saved Charts and Reports', - width: 1000, - - initComponent: function () { - var aspectRatio = 11 / 17; - - this.chartReportStore = new Ext.data.JsonStore({ - // store configs - autoDestroy: true, - url: XDMoD.REST.url + '/summary/savedchartsreports', - // reader configs - root: 'data', - idProperty: 'name', - autoLoad: true, - fields: [ - 'name', - 'report_id', - 'url', - 'config', - 'type', - { name: 'recordid', type: 'int' }, - 'ts' - ], - sortInfo: { - field: 'ts', - direction: 'DESC' - } - - }); - - var searchField = new Ext.form.TwinTriggerField({ - xtype: 'twintriggerfield', - validationEvent: false, - validateOnBlur: false, - trigger1Class: 'x-form-clear-trigger', - trigger2Class: 'x-form-search-trigger', - hideTrigger1: true, - enableKeyEvents: true, - emptyText: 'Search', - store: this.chartReportStore, - onTrigger1Click: function () { - this.store.clearFilter(); - this.el.dom.value = ''; - this.triggers[0].hide(); - }, - onTrigger2Click: function () { - var v = this.getRawValue(); - if (v.length < 1) { - this.onTrigger1Click(); - return; - } - this.store.filter('name', v, true, false); - this.triggers[0].show(); - }, - listeners: { - scope: this, - specialkey: function (field, e) { - // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN, - // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN - if (e.getKey() === e.ENTER) { - searchField.onTrigger2Click(); - } - } - } - }); - - this.chartReportGrid = new Ext.grid.GridPanel({ - store: this.chartReportStore, - border: false, - monitorResize: true, - autoScroll: true, - viewConfig: { - forceFit: true - }, - colModel: new Ext.grid.ColumnModel({ - columns: [ - { header: 'Name', dataIndex: 'name', width: 250 }, - { header: 'Type', dataIndex: 'type' }, - { - header: 'Last Modified', - dataIndex: 'ts', - renderer: function (value) { - if (value === '0') { - return 'Unknown'; - } - return Ext.util.Format.date(new Date(value * 1000).toString(), 'Y-m-d h:i:s'); - } - } - ], - defaults: { - sortable: true, - menuDisabled: true - } - }), - tbar: { - items: [ - searchField, - { - iconCls: 'refresh', - text: 'Refresh', - scope: this, - handler: function () { - this.chartReportStore.reload(); - } - } - ] - }, - selModel: new Ext.grid.RowSelectionModel({ - singleSelect: true, - listeners: { - rowselect: function (selModel, index, r) { - selModel.clearSelections(); - if (r.data.type === 'Chart') { - var config = Ext.util.JSON.decode(r.data.config); - XDMoD.Module.MetricExplorer.setConfig(config, r.data.name, false); - } else if (r.data.type === 'Report') { - CCR.xdmod.ui.reportGenerator.fireEvent('load_report', r.data.report_id); - } - } - } - }) - }); - - this.height = this.width * aspectRatio; - this.items = [this.chartReportGrid]; - this.tools = [ - { - id: 'help', - qtip: 'Porlet shows a list of saved charts and reports.', - qwidth: 60 - } - ]; - XDMoD.Modules.SummaryPortlets.SavedChartsReportsPortlet.superclass.initComponent.apply(this, arguments); - } -}); - -/** -* The Ext.reg call is used to register an xtype for this class so it -* can be dynamically instantiated -*/ -Ext.reg('SavedChartsReportsPortlet', XDMoD.Modules.SummaryPortlets.SavedChartsReportsPortlet); diff --git a/html/gui/js/modules/summary/SummaryStatisticsPortlet.js b/html/gui/js/modules/summary/SummaryStatisticsPortlet.js deleted file mode 100644 index 7a249ac8e3..0000000000 --- a/html/gui/js/modules/summary/SummaryStatisticsPortlet.js +++ /dev/null @@ -1,208 +0,0 @@ -/** - * XDMoD.Modules.SummaryPortlets.SummaryStatisticsPortlet - * - * - */ - -Ext.namespace('XDMoD.Modules.SummaryPortlets'); - -XDMoD.Modules.SummaryPortlets.SummaryStatisticsPortlet = Ext.extend(Ext.ux.Portlet, { - - layout: 'fit', - autoScroll: true, - baseTitle: 'Summary Statistics', - tbar: { - border: false, - cls: 'xd-toolbar' - }, - - /** - * The styling that will be applied to the summary statistic toolbar item - * headers. - */ - keyStyle: { - marginLeft: '2px', - marginRight: '2px', - fontSize: '11px', - textAlign: 'center' - }, - - /** - * The styling that will be applied to the summary statistic toolbar item - * values. - */ - valueStyle: { - marginLeft: '2px', - marginRight: '2px', - textAlign: 'center', - fontFamily: 'arial,"Times New Roman",Times,serif', - fontSize: '11px', - letterSpacing: '0px' - }, - - /** - * - */ - initComponent: function () { - var self = this; - - var aspectRatio = 0.8; - this.height = this.width * aspectRatio; - var title = this.baseTitle; - - var dateRanges = CCR.xdmod.ui.DurationToolbar.getDateRanges(); - for (var i = 0; i < dateRanges.length; i++) { - var dateRange = dateRanges[i]; - if (dateRange.text === this.config.timeframe) { - this.config.start_date = this.formatDate(dateRange.start); - this.config.end_date = this.formatDate(dateRange.end); - title = this.baseTitle + ' - ' + this.config.start_date + ' to ' + this.config.end_date; - } - } - - this.setTitle(title); - - this.summaryStatisticsStore = new CCR.xdmod.CustomJsonStore({ - - root: 'data', - totalProperty: 'totalCount', - autoDestroy: true, - autoLoad: false, - successProperty: 'success', - messageProperty: 'message', - - fields: [ - 'job_count', - 'active_person_count', - 'active_pi_count', - 'total_waitduration_hours', - 'avg_waitduration_hours', - 'total_cpu_hours', - 'avg_cpu_hours', - 'total_su', - 'avg_su', - 'min_processors', - 'max_processors', - 'avg_processors', - 'total_wallduration_hours', - 'avg_wallduration_hours', - 'gateway_job_count', - 'active_allocation_count', - 'active_institution_count', - 'statistics_formats' - ], - - proxy: new Ext.data.HttpProxy({ - method: 'GET', - url: XDMoD.REST.url + '/summary/statistics', - listeners: { - - /** - * - * @param proxy {Ext.data.DataProxy} - * @param request {Ext.data.Request} - */ - load: function (proxy, request) { - var formats = request.reader.jsonData.formats; - var data = request.reader.jsonData.data; - - // only populate the statistics if we have all the data - // we require. - if (formats && data.length) { - self.populateSummaryStatistics(formats, data[0]); - } - } // load: function (proxy, request) { - } // listeners: { - }) // proxy: new Ext.data.HttpProxy({ - }); // this.summaryStatisticsStore - - XDMoD.Modules.SummaryPortlets.SummaryStatisticsPortlet.superclass.initComponent.apply(this, arguments); - }, // initComponent - - listeners: { - /** - * This event fires after the component has been rendered. This will only - * occur once per page refresh. - */ - afterrender: function () { - this.summaryStatisticsStore.load({ - params: { - start_date: this.config.start_date, - end_date: this.config.end_date - } - }); - } - }, // listeners { - - /** - * Populates this components top toolbar w/ the series of summary statistics - * as defined in `data`, formatted via the entries in `formats`. - * - * @param formats {object[]} - * @param data {object} - */ - populateSummaryStatistics: function (formats, data) { - // Clear the top toolbar before re-populating it. - this.getTopToolbar().removeAll(); - - Ext.each(formats, function (itemGroup) { - var itemTitles = []; - var items = []; - - Ext.each(itemGroup.items, function (item) { - var itemData = data[item.fieldName]; - var itemNumber; - - if (itemData) { - if (item.numberType === 'int') { - itemNumber = parseInt(itemData, 10); - } else if (item.numberType === 'float') { - itemNumber = parseFloat(itemData); - } - - itemTitles.push({ - xtype: 'tbtext', - text: item.title + ':', - style: this.keyStyle - }); - - items.push({ - xtype: 'tbtext', - text: itemNumber.numberFormat(item.numberFormat), - style: this.valueStyle - }); - } // if (itemData) - }, this); // Ext.each(itemGroup.items, ... - - if (items.length > 0) { - this.getTopToolbar().add({ - xtype: 'buttongroup', - columns: items.length, - title: itemGroup.title, - items: itemTitles.concat(items) - }); - } - }, this); - - // make sure that we force the toolbar to re-lay its self out. - this.getTopToolbar().doLayout(); - }, // populateSummaryStatistics - - /** - * Returns a consistently formatted string from the provided `date`. - * Ex. `2019-01-01` - * - * @param date {Date} the date to use when building the formatted string. - * @returns {string} a `YYYY-MM-DD` formatted string based on the `date` - * parameter. - */ - formatDate: function (date) { - return date.getFullYear() + '-' + ('' + (date.getMonth() + 1)).padStart(2, '0') + '-' + ('' + date.getDate()).padStart(2, '0'); - } // formatDate: function(date) { -}); // XDMoD.Modules.SummaryPortlets.CenterHealthPortlet = Ext.extend(Ext.ux.Portlet, { - -/** - * The Ext.reg call is used to register an xtype for this class so it - * can be dynamically instantiated - */ -Ext.reg('SummaryStatisticsPortlet', XDMoD.Modules.SummaryPortlets.SummaryStatisticsPortlet); diff --git a/html/gui/js/profile_editor/ProfileEditor.js b/html/gui/js/profile_editor/ProfileEditor.js index 7546c778ca..9e84f4e923 100644 --- a/html/gui/js/profile_editor/ProfileEditor.js +++ b/html/gui/js/profile_editor/ProfileEditor.js @@ -100,7 +100,7 @@ XDMoD.ProfileEditor = Ext.extend(Ext.Window, { })); } - if (!CCR.xdmod.ui.tgSummaryViewer.usesToolbar) { + if (CCR.xdmod.ui.tgSummaryViewer.title === 'Dashboard') { tabItems.push({ title: 'Settings', height: 316, @@ -116,11 +116,11 @@ XDMoD.ProfileEditor = Ext.extend(Ext.Window, { frame: true, items: [{ xtype: 'button', - fieldLabel: 'Summary Tab Panel Layout', + fieldLabel: 'Dashboard Layout', text: 'Reset to Default', handler: function (button) { Ext.Ajax.request({ - url: XDMoD.REST.url + '/summary/layout', + url: XDMoD.REST.url + '/dashboard/layout', method: 'DELETE', success: function () { button.setDisabled(true); diff --git a/tests/artifacts/xdmod/report_builder/output/cd_enum_templates.json b/tests/artifacts/xdmod/report_builder/output/cd_enum_templates.json index 5cd6e019b9..b28eaf3939 100644 --- a/tests/artifacts/xdmod/report_builder/output/cd_enum_templates.json +++ b/tests/artifacts/xdmod/report_builder/output/cd_enum_templates.json @@ -10,8 +10,8 @@ }, { "id": "2", - "name": "Summary Tab Report", - "description": "Summary Tab Report", + "name": "Dashboard Tab Report", + "description": "Dashboard Tab Report", "use_submenu": "0" } ], diff --git a/tests/artifacts/xdmod/report_builder/output/cs_enum_templates.json b/tests/artifacts/xdmod/report_builder/output/cs_enum_templates.json index ef0f7f689f..477bb96eb6 100644 --- a/tests/artifacts/xdmod/report_builder/output/cs_enum_templates.json +++ b/tests/artifacts/xdmod/report_builder/output/cs_enum_templates.json @@ -4,8 +4,8 @@ "templates": [ { "id": "3", - "name": "Summary Tab Report", - "description": "Summary Tab Report", + "name": "Dashboard Tab Report", + "description": "Dashboard Tab Report", "use_submenu": "0" } ], diff --git a/tests/artifacts/xdmod/ui/output/reportGenerator.json b/tests/artifacts/xdmod/ui/output/reportGenerator.json index 95d2f8ebdb..7ff3b613c3 100644 --- a/tests/artifacts/xdmod/ui/output/reportGenerator.json +++ b/tests/artifacts/xdmod/ui/output/reportGenerator.json @@ -142,8 +142,8 @@ ] }, { - "name": "Summary Tab Report", - "created_name": "Summary Tab Report", + "name": "Dashboard Tab Report", + "created_name": "Dashboard Tab Report", "created_reports_count": 8, "reports_created": 1, "delivery_format": "PDF", diff --git a/tests/ui/test/specs/xdmod/about.js b/tests/ui/test/specs/xdmod/about.js index 9e97f3ff1d..5230e5004a 100644 --- a/tests/ui/test/specs/xdmod/about.js +++ b/tests/ui/test/specs/xdmod/about.js @@ -24,7 +24,7 @@ describe('About', function about() { it('SUPReMM', function checkNavEntrySUPReMM() { Abt.checkTab('SUPReMM'); }); - it.skip('Roadmap', function checkNavEntryXDMoD() { + it('Roadmap', function checkNavEntryXDMoD() { Abt.checkRoadmap(); }); it('Team', function checkNavEntryXDMoD() { @@ -73,7 +73,7 @@ describe('About', function about() { it('SUPReMM', function checkNavEntrySUPReMM() { Abt.checkTab('SUPReMM'); }); - it.skip('Roadmap', function checkNavEntryXDMoD() { + it('Roadmap', function checkNavEntryXDMoD() { Abt.checkRoadmap(); }); it('Team', function checkNavEntryXDMoD() { diff --git a/tests/ui/test/specs/xdmod/about.page.js b/tests/ui/test/specs/xdmod/about.page.js index 6e351bab78..ba23b7408b 100644 --- a/tests/ui/test/specs/xdmod/about.page.js +++ b/tests/ui/test/specs/xdmod/about.page.js @@ -24,8 +24,7 @@ class About { expect(err).to.be.a('undefined'); expect(result).to.not.be.a('null'); }); - browser.waitForExist('.trello-lists', 30000); - browser.waitForText('.trello-lists', 30000); + browser.waitForExist('.board-tile-container', 30000); browser.frameParent(); }