From d1f7dfc97af9c4faa2709549876086b93e8a439a Mon Sep 17 00:00:00 2001 From: ryanrath Date: Mon, 15 Jul 2019 15:09:52 -0400 Subject: [PATCH] Novice User Interface - Center Report Card (#63) * Added CenterReportCardPortlet and required infrastructure Added a new Portlet, the Center Report Card Portlet, for the Novice User view. This portlet displays App Kernel run information ( Failed, Under Performing, In Control, and Over Performing ) for a given time period. If the user clicks on one of the presented rows, they will be taken to the `App Kernels-> Performance Map` tab. It's duration toolbar will be updated to match the start / end date the portlet is using and the row that corresponds to the resource / app kernel the user clicked on will be selected. - To support this new feature, the `Performance Map` was moved from the Internal Dashboard to be a new sub tab of `App Kernels`. - A new function, `getPerformanceMapRaw`, was added to `AppKernelControllerProvider`. - `AppKernels.js` was modified so that it knows how to activate a sub tab, if one is provided. - A number of masking / unmasking calls were added so that the user is kept in the loop as to when XDMoD is busy retrieving data. * Updating build files Just updating the build files so that they're included in the rpm. * Updated to remove XSEDE dependency The `Compliance` class is located in the `XSEDE` module but is utilized here in the `AppKernels` module. I believe we want users to be able to utilize the `AppKernels` module without having `XSEDE` installed, so this should help us achieve that goal. * Reformatted per eslint feedback * Adding a CSS file to this modules assets We were already including the `ColumnHeaderGroup.js` file here, now we also include `ColumnHeaderGroup.css`. * Added documentation to .travis file. Just documenting the previously added line. * Updates to allow additional filtering of app kernel data Added the ability for the the AppKernel Performance Map to be filtered by a set of authorized resources or the set of resources associated with a User's organization. * Removed userOrganization and cleaned up resource_ids * Updates to the `getRawPerformanceMap` function - Added some additional documentation / throws clause to reflect requiring the user calling this endpoint to have either the Center Director or Center Staff acl. - Updated the function to require the authenticated user making the request to have the Center Director or Center Staff acl. - Replaced the use of `MetricExplorer::getDimensionValues` with `XDUser::getResources` per research / discussion w/ @jpwhite4. * Updating the authorization method for `getRawPerformanceMap` We require that either Center Director's *or* Center Staff be allowed to utilize this endpoint. The original `authorize($request, $requirements)` function call instead ensures that the user have *all* of the acls supplied. * Update authorization logic & resource parameter - authorization logic update: derrrrr.... I r know logics. - `resource` param for `PerformanceMap`: needed to massage the form it was provided to `PerformanceMap`. * Fixing path to ColumnHeaderGroup.css * Pinning dist so that php 5.4 will work * eslint fix * asfjlk * Added documentation & formatted some sql * Standardizing authorization logic We have the same logic in `XDUser::getResources` just updating so that they're the same. --- .travis.yml | 4 + build.json | 3 +- classes/AppKernel/AppKernelDb.php | 108 ++- classes/AppKernel/PerformanceMap.php | 534 ++++++------ .../AppKernelControllerProvider.php | 302 ++++--- configuration/assets.d/appkernels.json | 7 +- .../appkernels-center_report_card.json | 28 + configuration/roles.json | 3 +- html/.eslintrc.json | 19 - html/gui/js/modules/AppKernels.js | 154 ++-- .../app_kernels/AppKerPerformanceMapPanel.js | 781 +++++++++++++++++ .../modules/app_kernels/AppKernelExplorer.js | 659 ++++++--------- .../js/modules/app_kernels/AppKernelViewer.js | 154 ++-- .../summary/CenterReportCardPortlet.js | 388 +++++++++ .../js/Arr/AppKerPerformanceMapPanel.js | 800 ------------------ .../js/Arr/AppKernelDashboardPanel.js | 65 +- libraries/appkernel.php | 8 +- xdmod-appkernels.spec.in | 1 + 18 files changed, 2264 insertions(+), 1754 deletions(-) create mode 100644 configuration/roles.d/appkernels-center_report_card.json delete mode 100644 html/.eslintrc.json create mode 100644 html/gui/js/modules/app_kernels/AppKerPerformanceMapPanel.js create mode 100644 html/gui/js/modules/summary/CenterReportCardPortlet.js delete mode 100644 html/internal_dashboard/js/Arr/AppKerPerformanceMapPanel.js diff --git a/.travis.yml b/.travis.yml index c793d00..ff4d4e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ sudo: false # Specify the build matrix language: php +dist: trusty php: - '5.4' - '7.0' @@ -28,5 +29,8 @@ before_install: git clone --depth=1 --branch="v1" https://github.com/ubccr/xdmod # Delegate the installation step to the shared Travis installation script install: .qa/travis/install.sh +# Need to make sure that the style config files are in place before the build step. +before_script: .qa/style/install.sh ./ + # Delegate the build step to the shared Travis build script script: .qa/travis/build.sh diff --git a/build.json b/build.json index 3c867d3..3a87261 100644 --- a/build.json +++ b/build.json @@ -42,7 +42,8 @@ "configuration/rest.d": "rest.d", "configuration/assets.d": "assets.d", "configuration/modules.d/appkernels.json": "modules.d/appkernels.json", - "configuration/linker.d/appkernels.json": "linker.d/appkernels.json" + "configuration/linker.d/appkernels.json": "linker.d/appkernels.json", + "configuration/roles.d/appkernels-center_report_card.json": "roles.d/appkernels-center_report_card.json" }, "etc/crond": { "configuration/cron.conf": "xdmod-appkernels" diff --git a/classes/AppKernel/AppKernelDb.php b/classes/AppKernel/AppKernelDb.php index 8789194..2d85dea 100644 --- a/classes/AppKernel/AppKernelDb.php +++ b/classes/AppKernel/AppKernelDb.php @@ -106,6 +106,7 @@ public function __construct(\Log $logger = NULL, $configSection = NULL) : $configSection); $this->db = DB::factory($configSection); + $this->modwDB = DB::factory('database'); $this->logger = $logger !== NULL ? $logger : \Log::singleton('null'); } // __construct() @@ -401,18 +402,12 @@ public function getResources($start_date = NULL, $end_date = NULL, array $pu_cou $access_to_some_resource=true; //get all associated organization - $organizations=array(); - if(in_array("cd",$roles)) - $organizations=array_merge($organizations,$user->getOrganizationCollection("cd")); - if(in_array("cs",$roles)) - $organizations=array_merge($organizations,$user->getOrganizationCollection("cs")); - - $organizations[]=$user->getPrimaryOrganization(); - $organizations[]=$user->getActiveOrganization(); + $organizations = array( + $user->getOrganizationID() + ); #get resource_id for all associated resources - $c=new \Compliance(); - $rr=$c->getResourceListing(date_format(date_sub(date_create(), date_interval_create_from_date_string("90 days")),'Y-m-d'),date_format(date_create(),'Y-m-d')); + $rr=$this->getResourceListing(date_format(date_sub(date_create(), date_interval_create_from_date_string("90 days")),'Y-m-d'),date_format(date_create(),'Y-m-d')); $organizations_resources_id=array(); foreach($rr as $r){ @@ -436,6 +431,99 @@ public function getResources($start_date = NULL, $end_date = NULL, array $pu_cou return $resources; } // getResources() + /** + * This function has been lifted from the XSEDE Modules Compliance class so that AppKernels can + * be used by Open Source Users. Retrieves a list of resources filtered by the optionally provided + * `$start_date` and `$end_date` + * + * @param string|null $start_date + * @param string|null $end_date + * @return mixed + */ + public function getResourceListing($start_date = null, $end_date = null) { + + // Order by descending end_date and processors + + $ts = ""; + + // Resources which have no end date are considered still active so use the max date + // possible. Resources are sorted in order of decreasing end date so this will keep active + // resources at the top of the list. + + $m = "CASE WHEN rf.end_date IS NULL THEN '9999-12-31' ELSE rf.end_date END"; + + $query_params = array(); + + if (isset($start_date) && isset($end_date)) { + + /* + + Account for ALL resources (in other words, an overlap between the supplied timeframe and the resource timeframe) + + S(t) E(t) + |----------------------------| + |------------------------| + S(r) E(r) + + + (t): Supplied timeframe + (r): A resource + + Overlap exists when S(r) <= E(t) AND E(r) >= S(t) + + */ + + $ts .= " AND rf.start_date <= :ts_start_date_lte AND ($m) >= :ts_end_date_gte"; + + $query_params[':ts_start_date_lte'] = $end_date; + $query_params[':ts_end_date_gte'] = $start_date; + + } + + $query = " + SELECT + rf.code, + rf.organization_id, + rf.id, + rt.description, + rt.abbrev, + CASE + WHEN rf.end_date IS NULL THEN 'N/A' + ELSE DATE_FORMAT(rf.end_date, '%Y-%m-%d') + END AS official_end_date, + $m AS resource_end_date, + DATE_FORMAT(rf.start_date, '%Y-%m-%d') AS resource_start_date, + CASE + WHEN rs.processors IS NULL THEN 0 + ELSE rs.processors + END AS processors + FROM + modw.resourcefact AS rf, + modw.resourcespecs rs, + modw.resourcetype AS rt + WHERE + rf.id = rs.resource_id + AND rf.resourcetype_id = rt.id + AND rt.abbrev IN ('HPC', 'HTC', 'DIC', 'Vis', 'Disk', 'Cloud') + AND rf.code NOT LIKE 'TG%' + $ts + AND UNIX_TIMESTAMP(:start_date_lte) >= rs.start_date_ts + AND ( + rs.end_date_ts IS NULL + OR UNIX_TIMESTAMP(:end_date_gte) <= rs.end_date_ts + ) + ORDER BY + rs.processors DESC, + resource_end_date DESC, + rf.code DESC" + ; + + $query_params[':start_date_lte'] = $end_date; + $query_params[':end_date_gte'] = $end_date; + + return $this->modwDB->query($query, $query_params); + }//getResourceListing + // -------------------------------------------------------------------------------- public function getProcessingUnits($start_date = NULL, $end_date = NULL, array $resource_ids = array(), array $metrics = array()) diff --git a/classes/AppKernel/PerformanceMap.php b/classes/AppKernel/PerformanceMap.php index a720efc..952e079 100644 --- a/classes/AppKernel/PerformanceMap.php +++ b/classes/AppKernel/PerformanceMap.php @@ -19,6 +19,13 @@ class PerformanceMap public $appKer; /** @param string[]|null*/ public $problemSize; /** @param string[]|null*/ + /** + * The organization id of the user who is requesting data from this object. + * + * @var int + */ + public $userOrganization; + public $perfMap; public $ak_shortnames; @@ -99,10 +106,25 @@ public function __construct($options) $this->ak_def_ids=$ak_def_ids; //get resource_ids + $sql = 'SELECT akr.resource_id, akr.resource, akr.nickname FROM mod_appkernel.resource akr'; + $params = array(); + + if(isset($this->resource)) { + $quotedResourceIds = array_reduce( + $this->resource['data'], + function ($carry, $item) use ($pdo) { + $carry[] = $pdo->quote($item['id']); + return $carry; + }, + array() + ); + $sql = "SELECT akr.resource_id, akr.resource, akr.nickname FROM mod_appkernel.resource akr " . + "WHERE akr.xdmod_resource_id IN (" . implode(', ', $quotedResourceIds) . ')'; + } + + $sqlres_tasdb=$pdo->query($sql, $params); + $resource_ids=array(); - $sql = "SELECT resource_id,resource,nickname - FROM resource;"; - $sqlres_tasdb=$pdo->query($sql); foreach($sqlres_tasdb as $row) { $resource_ids[$row['nickname']]=$row['resource_id']; @@ -121,8 +143,6 @@ public function __construct($options) $this->perfMap=$this->getMap(); - //print_r($this->perfMap); - //print_r($this->appKer); } /** * Generate html-report with performance map table @@ -145,19 +165,12 @@ public function make_report($internal_dashboard_user=false) $runsStatus=$this->perfMap['runsStatus']; $rec_dates=$this->perfMap['rec_dates']; - $controlThreshold=$this->controlThreshold*$this->controlThresholdCoeff; //print table $tdStyle_Resource='style=""'; - $tdStyle_AppKer='style="""'; $tdStyle_ProblemSize='style=""'; $tdStyle_Day='style="" align="center"'; - $tdStyle_Day_Empty='style="background-color:white;"'; - $tdStyle_Day_Good='style="background-color:#B0FFC5;"'; - $tdStyle_Day_Warning='style="background-color:#FFFF99;"'; - $tdStyle_Day_Error='style="background-color:#FFB0C4;"'; - $tdStyle=array( ' '=>'style="background-color:white;"', 'F'=>'style="background-color:#FFB0C4;"', @@ -168,16 +181,9 @@ public function make_report($internal_dashboard_user=false) 'R'=>'style="background-color:#F781F3;"' ); - //'
' + value + '
'; - //'
' + value + '
'; - //'
' + value + '
'; $message=''; $message.='

Table 3. Performance Heat Map of All App Kernels on Each System

'; - /*$message.='KEY: For each day a triplet of integers are used to represent the status of a given application kernel run for each node size. -The first integer is the number of successful runs, the second the number of out of control runs, and the third the number of failed runs. -Color coding is as follows: RED - failed run, YELLOW - out of -control, GREEN - no out of control or failed run.';*/ $message.='KEY: Each day is summarized in a table cell as pair of a symbol and a number. The symbol represents the status of last application kernel' . ' execution on that day and the number shows the total number of runs. Each cell is colored according to the status of last application kernel run.' . ' The description of the codes are:

' @@ -202,7 +208,6 @@ public function make_report($internal_dashboard_user=false) $totalColumns=1+count($rec_dates)+1; //body - $headerCount=0; foreach ($runsStatus as $resource => $val0) { //table header @@ -231,8 +236,6 @@ public function make_report($internal_dashboard_user=false) $message.=''; $message.=''; - //$message.='Resource'; - //$message.='App Kernel'; $message.='Nodes'; foreach ($rec_dates as $rec_date) @@ -260,8 +263,6 @@ public function make_report($internal_dashboard_user=false) { $message.=''; $tag='perfMap_'.$resource.'_'.$appKer.'_'.$problemSize; - //$message.=''.$resource.''; - //$message.=''.$this->ak_shortnames[$appKer].''; $message.=''.$problemSize.''; foreach ($val2 as $rec_date => $taskStateGroup) { @@ -269,20 +270,6 @@ public function make_report($internal_dashboard_user=false) if($totalRuns>0) { - $inControlRuns=count($taskStateGroup->inControlRuns); - $underPerformingRuns=count($taskStateGroup->underPerformingRuns); - $overPerformingRuns=count($taskStateGroup->overPerformingRuns); - $controlIntervalRuns=count($taskStateGroup->controlIntervalRuns); - $noControlInfoRuns=count($taskStateGroup->noControlInfoRuns); - $failedRuns=count($taskStateGroup->failedRuns); - - $inControlRunsID=end($taskStateGroup->inControlRuns); - $underPerformingRunsID=end($taskStateGroup->underPerformingRuns); - $overPerformingRunsID=end($taskStateGroup->overPerformingRuns); - $controlIntervalRunsID=end($taskStateGroup->controlIntervalRuns); - $noControlInfoRunsID=end($taskStateGroup->noControlInfoRuns); - $failedRunsID=end($taskStateGroup->failedRuns); - $last_task=end($taskStateGroup->tasks); $message.="summary]}>"; $message.="id}\">"; @@ -478,12 +465,6 @@ public function get_summary_for_days($days) $message.='Number of out of control runs: '. $outOfControlRuns.'
'; $message.='Number of runs within threshold: '. $inControlRuns.'
'; - //$message.='

Performance Map

'; - - //$message.=''; - //table header - //$message.=''; - //$message.='
'; $result=array( 'message'=>$message, 'messageTable'=>$messageTable, @@ -509,138 +490,213 @@ private function getMap() $run_date = clone $this->start_date; $day_interval = new DateInterval('P1D'); - while ($run_date<=$end_date) - { - $rec_dates[]=$run_date->format('Y/m/d'); + while ($run_date <= $end_date) { + $rec_dates[] = $run_date->format('Y/m/d'); $run_date->add($day_interval); } //prep arrays for table - $runsStatus=array(); - /*foreach ($resources as $resource) { - foreach ($appKers as $appKer) { - foreach ($problemSizes as $problemSize) { - foreach ($rec_dates as $rec_date) { - $runsStatus[$resource][$appKer][$problemSize][$rec_date]=array(); - } - } - } - }*/ + $runsStatus = array(); + //add one more day for ranges - $rec_dates[]=$run_date->format('Y/m/d'); - $end_date_not_included = $run_date->format('Y/m/d'); + $rec_dates[] = $run_date->format('Y/m/d'); //get appKer instances + $sql = "SELECT ak_id,num_units,ak_def_id,name FROM app_kernel ORDER BY ak_id ASC;"; + $sqlres_tasdb = $pdo->query($sql); - //get information from /tas-db1 - //resource_id - $resource_ids=array(); - $sql = "SELECT resource_id,resource,nickname - FROM resource - ORDER BY resource_id ASC;"; - $sqlres_tasdb=$pdo->query($sql); - foreach($sqlres_tasdb as $row) - { - $resource_ids[$row['nickname']]=$row['resource_id']; + $ak_ids = array(); + foreach ($sqlres_tasdb as $row) { + $ak_ids[$row['name']][$row['num_units']] = $row['ak_id']; } - //app_kernel - $ak_ids=array(); - $sql = "SELECT ak_id,num_units,ak_def_id,name - FROM app_kernel - ORDER BY ak_id ASC;"; - $sqlres_tasdb=$pdo->query($sql); - foreach($sqlres_tasdb as $row) - { - $ak_ids[$row['name']][$row['num_units']]=$row['ak_id']; + + // Retrieve the control state + $sql = <<= :start_date AND + aki.collected < :end_date + ORDER BY aki.collected ASC; +SQL; + $params = array( + ':start_date' => $start_date->format('Y/m/d'), + ':end_date' => $end_date->format('Y/m/d') + ); + + // If a userOrganization has been provided then filter the results on them. + if (isset($this->userOrganization)) { + $sql = <<= :start_date AND + aki.collected < :end_date AND + rf.organization_id = :organization_id + ORDER BY aki.collected ASC; + +SQL; + $params[':organization_id'] = $this->userOrganization; + } elseif (isset($this->resource_ids)) { + $resourceIds = implode(', ', $this->resource_ids); + $sql = <<= :start_date AND + aki.collected < :end_date AND + aki.resource_id IN ($resourceIds) + ORDER BY aki.collected ASC; +SQL; + } - $controlState=array(); - $sql = "SELECT ak_id,collected,resource_id,instance_id,status,controlStatus - FROM ak_instance - WHERE '".$start_date->format('Y/m/d')."' <=collected AND collected < '$end_date_not_included' - ORDER BY collected ASC;"; - $sqlres_tasdb=$pdo->query($sql); - foreach($sqlres_tasdb as $row) - { - $controlState[$row['resource_id']][$row['ak_id']][$row['collected']]=$row['controlStatus']; + + $rows = $pdo->query($sql, $params); + + $controlState = array(); + foreach ($rows as $row) { + $controlState[$row['resource_id']][$row['ak_id']][$row['collected']] = $row['controlStatus']; + } + + // Retrieve the main set of data used to create the map. + $sql = <<= :start_date + AND axi.collected < :end_date + ORDER BY axi.collected ASC; +SQL; + $params = array( + ':start_date' => $start_date->format('Y/m/d'), + ':end_date' => $end_date->format('Y/m/d') + + ); + + // If we have resource id's then we should additionally filter on them. + if (count($this->resource_ids) > 0) { + + $quotedResources = implode( + ",", + array_map( + function ($value) use ($arr_db) { + return $arr_db->quote($value); + }, + array_keys($this->resource_ids) + ) + ); + + $sql = <<= :start_date + AND axi.collected < :end_date + AND axi.resource IN ( $quotedResources ) + ORDER BY axi.collected ASC; +SQL; } //get information from appkernel-db - $sql = "SELECT instance_id,collected,resource,reporter,reporternickname,status - FROM mod_akrr.akrr_xdmod_instanceinfo - WHERE '".$start_date->format('Y/m/d')."' <=collected AND collected < '$end_date_not_included' - ORDER BY collected ASC;"; - $sqlres=$arr_db->query($sql); - - $taskStateGroupOptions=array( - 'controlThreshold'=>$this->controlThreshold, - 'controlThresholdCoeff'=>$this->controlThresholdCoeff, + $sqlres = $arr_db->query($sql, $params); + + $taskStateGroupOptions = array( + 'controlThreshold' => $this->controlThreshold, + 'controlThresholdCoeff' => $this->controlThresholdCoeff, ); - foreach($sqlres as $row) - { - $rec_date=date_format(date_create($row['collected']),'Y/m/d'); - $resource=$row['resource']; - $appKer=$row['reporter']; - $problemSize=end(explode('.', $row['reporternickname'])); - $collected=$row['collected']; - - $resource_id=arrayValue($resource,$resource_ids); - $ak_id=null; - if(array_key_exists($appKer,$ak_ids)) - if(array_key_exists($problemSize,$ak_ids[$appKer])) - $ak_id=$ak_ids[$appKer][$problemSize]; - if($ak_id===null)continue; - - $task=new TaskState($taskStateGroupOptions); - $task->id=$row['instance_id']; - $task->collected=DateTime::createFromFormat('Y-m-d H:i:s',$row['collected']); - $task->status=intval($row['status']); - $task->controlStatus='undefined'; - - if((!is_null($resource_id)) && (!is_null($ak_id))) - if(array_key_exists($resource_id,$controlState)) - if(array_key_exists($ak_id,$controlState[$resource_id])) - if(array_key_exists($collected,$controlState[$resource_id][$ak_id])) - { - $task->controlStatus=$controlState[$resource_id][$ak_id][$collected]; + + foreach ($sqlres as $row) { + $rec_date = date_format(date_create($row['collected']), 'Y/m/d'); + $resource = $row['resource']; + $appKer = $row['reporter']; + $problemSize = end(explode('.', $row['reporternickname'])); + $collected = $row['collected']; + + $resource_id = arrayValue($resource, $this->resource_ids); + $ak_id = null; + if (array_key_exists($appKer, $ak_ids) && array_key_exists($problemSize, $ak_ids[$appKer])) { + $ak_id = $ak_ids[$appKer][$problemSize]; } - //filter the values - if($this->resource!=NULL) - if(!in_array($resource,$this->resource)) - continue; - if($this->appKer!=NULL) - if(!in_array($this->ak_shortnames[$appKer],$this->appKer)) - continue; - if($this->problemSize!=NULL) - if(!in_array($problemSize,$this->problemSize)) - continue; - - //init entries in $runsStatus if needed - if(!array_key_exists($resource,$runsStatus)) - $runsStatus[$resource]=array(); - if(!array_key_exists($appKer,$runsStatus[$resource])) - $runsStatus[$resource][$appKer]=array(); - if(!array_key_exists($problemSize,$runsStatus[$resource][$appKer])) - { - $runsStatus[$resource][$appKer][$problemSize]=array(); - for($i=0;$iresource_ids) && !array_key_exists($resource, $this->resource_ids)) || + (isset($this->appKer) && !in_array($this->ak_shortnames[$appKer], $this->appKer)) || + (isset($this->problemSize) && !in_array($problemSize, $this->problemSize)) + ) { + continue; + } + + $task = new TaskState($taskStateGroupOptions); + $task->id = $row['instance_id']; + $task->collected = DateTime::createFromFormat('Y-m-d H:i:s', $row['collected']); + $task->status = intval($row['status']); + $task->controlStatus = 'undefined'; + + if (isset($resource_id)) { + if (isset($ak_id) && + array_key_exists($resource_id, $controlState) && + array_key_exists($ak_id, $controlState[$resource_id]) && + array_key_exists($collected, $controlState[$resource_id][$ak_id])) { + $task->controlStatus = $controlState[$resource_id][$ak_id][$collected]; + } + + //init entries in $runsStatus if needed + if (!array_key_exists($resource, $runsStatus)) { + $runsStatus[$resource] = array(); + } + + if (!array_key_exists($appKer, $runsStatus[$resource])) { + $runsStatus[$resource][$appKer] = array(); + } + + if (!array_key_exists($problemSize, $runsStatus[$resource][$appKer])) { + $runsStatus[$resource][$appKer][$problemSize] = array(); + for ($i = 0; $i < count($rec_dates) - 1; $i++) { + $runsStatus[$resource][$appKer][$problemSize][$rec_dates[$i]] = new TaskStateGroup($taskStateGroupOptions); + } + } + + $runsStatus[$resource][$appKer][$problemSize][$rec_date]->add_task($task); } - $runsStatus[$resource][$appKer][$problemSize][$rec_date]->add_task($task); } //sort ksort($runsStatus); - foreach ($runsStatus as $resource => $val1) - { + foreach ($runsStatus as $resource => $val1) { + ksort($runsStatus[$resource]); - foreach ($runsStatus[$resource] as $appKer => $val2) - { + foreach ($runsStatus[$resource] as $appKer => $val2) { + ksort($runsStatus[$resource][$appKer]); - foreach ($runsStatus[$resource][$appKer] as $problemSize => $val3) - { + foreach ($runsStatus[$resource][$appKer] as $problemSize => $val3) { + ksort($runsStatus[$resource][$appKer][$problemSize]); - foreach ($runsStatus[$resource][$appKer][$problemSize] as $d => $val4) - { + foreach ($runsStatus[$resource][$appKer][$problemSize] as $d => $val4) { + $runsStatus[$resource][$appKer][$problemSize][$d]->sort_by_collection_time(); $runsStatus[$resource][$appKer][$problemSize][$d]->process(); } @@ -650,33 +706,60 @@ private function getMap() //remove last date array_pop($rec_dates); - #print_r($runsStatus); - return array('rec_dates'=>$rec_dates,'runsStatus'=>$runsStatus); + + return array( + 'rec_dates' => $rec_dates, + 'runsStatus' => $runsStatus + ); } + + /** + * Get the Performance Map data, formatted for display via the `Performance Map` tab web + * element. + * + * @return array formatted as: + * { + * "success": boolean, ### always true. + * "response": { + * "metaData": { + * ### Data meant for the ExtJS component that is responsible for displaying this + * ### information. + * }, + * "results": [ + * { + * "resource": string, ### The name of the resource the app kernel was run on. + * "appKer": string, ### The human friendly name of the app kernel run. + * "problemSize": number, ### Number of nodes used. + * "": string, ### Date this app kernel was run. + * "-IDs-F": [ + * ### List of Failed Run Ids + * ], + * "-IDs-U": [ + * ### List of Under Performing Run Ids + * ], + * "-IDs-N": [ + * ### List of In Control Run Ids + * ], + * "-IDs-O": [ + * ### List of Over Performing Run Ids + * ], + * "-IDs-C": [ + * ### List of Control Interval Run Ids + * ], + * "-IDs-R": [ + * ### List of Run Ids with No Control Info + * ] + * } + * ] + * }, + * "count": number ### Count of 'response' + * } + */ public function getMapForWeb() { - $resources=NULL; - $appKers=NULL; - $problemSizes=NULL; - if($this->resource!==NULL){ - $resources=$this->resource; - } - if($this->appKer!==NULL){ - $appKers=array(); - foreach ($this->appKer as $ak_short) { - if (array_key_exists($ak_short, $this->ak_fullnames)){ - $appKers[]=$this->ak_fullnames[$ak_short]; - }else{ - $appKers[]=$ak_short; - } - } - } - if($this->problemSize!==NULL){ - $problemSizes=$this->problemSize; - } - $response = array(); - //pack metaData + + // Initialize the metaData to be returned. $response['metaData'] = array( 'root' => 'response', 'successProperty' => 'success', @@ -687,6 +770,8 @@ public function getMapForWeb() array('name' => 'problemSize', 'type' => 'string'), ) ); + + // Add additional metadata fields for each recorded date. foreach ($this->perfMap['rec_dates'] as $rec_date) { $response['metaData']['fields'][] = array('name' => $rec_date, 'type' => 'string'); $response['metaData']['fields'][] = array('name' => $rec_date . '-IDs-F', 'type' => 'string'); @@ -696,81 +781,54 @@ public function getMapForWeb() $response['metaData']['fields'][] = array('name' => $rec_date . '-IDs-C', 'type' => 'string'); $response['metaData']['fields'][] = array('name' => $rec_date . '-IDs-R', 'type' => 'string'); } - //pack results - $runsStatus=$this->perfMap['runsStatus']; + + // Begin packing the results $results = array(); - if($resources===NULL){ - $resources=array_keys($runsStatus); - } - foreach ($resources as $resource) { - if($appKers===NULL){ - $appKers=array_keys($runsStatus[$resource]); - } - foreach ($appKers as $appKer) { - if($problemSizes===NULL){ - $problemSizes=array_keys($runsStatus[$resource][$appKer]); - } - $appKer2 = $appKer; - if (array_key_exists($appKer, $this->ak_shortnames)){ - $appKer2 = $this->ak_shortnames[$appKer]; + + $runsStatus=$this->perfMap['runsStatus']; + foreach($runsStatus as $resource => $runData) { + foreach($runData as $appKernel => $nodeCountData) { + // We default the value displayed for the user to the full style name. + // Ex. xdmod.benchmark.io.ior + // But we'd prefer to show them the `shortName`. + // Ex. IOR + $appKernelDisplay = $appKernel; + if (array_key_exists($appKernel, $this->ak_shortnames)) { + $appKernelDisplay = $this->ak_shortnames[$appKernel]; } - foreach ($problemSizes as $problemSize) { - if(!array_key_exists($resource,$runsStatus)){ - $result[$rec_date] = ' '; - continue; - } - if(!array_key_exists($appKer,$runsStatus[$resource])){ - $result[$rec_date] = ' '; - continue; - } - if(!array_key_exists($problemSize,$runsStatus[$resource][$appKer])){ - $result[$rec_date] = ' '; - continue; - } + foreach($nodeCountData as $problemSize => $problemSizeData) { $result = array( 'resource' => $resource, - 'appKer' => $appKer2, - 'problemSize' => $problemSize, + 'appKer' => $appKernelDisplay, + 'problemSize' => $problemSize ); - foreach ($this->perfMap['rec_dates'] as $rec_date) { - if(!array_key_exists($resource,$runsStatus)){ - $result[$rec_date] = ' '; - continue; - } - if(!array_key_exists($appKer,$runsStatus[$resource])){ - $result[$rec_date] = ' '; - continue; - } - if(!array_key_exists($problemSize,$runsStatus[$resource][$appKer])){ - $result[$rec_date] = ' '; - continue; - } - if(!array_key_exists($rec_date,$runsStatus[$resource][$appKer][$problemSize])){ - $result[$rec_date] = ' '; - continue; - } - $tg=$runsStatus[$resource][$appKer][$problemSize][$rec_date]; - $nRuns = count($tg->tasks); - if($nRuns===0){ + + foreach($problemSizeData as $rec_date => $taskGroup) { + $runs = count($taskGroup->tasks); + if ($runs === 0) { $result[$rec_date] = ' '; continue; } - $result[$rec_date] = $tg->summary.'/'.$nRuns; - $result[$rec_date . '-IDs-F'] = $tg->failedRuns; - $result[$rec_date . '-IDs-U'] = $tg->underPerformingRuns; - $result[$rec_date . '-IDs-N'] = $tg->inControlRuns; - $result[$rec_date . '-IDs-O'] = $tg->overPerformingRuns; - $result[$rec_date . '-IDs-C'] = $tg->controlIntervalRuns; - $result[$rec_date . '-IDs-R'] = $tg->noControlInfoRuns; + + $result[$rec_date] = $taskGroup->summary . '/' . $runs; + $result[$rec_date . '-IDs-F'] = $taskGroup->failedRuns; + $result[$rec_date . '-IDs-U'] = $taskGroup->underPerformingRuns; + $result[$rec_date . '-IDs-N'] = $taskGroup->inControlRuns; + $result[$rec_date . '-IDs-O'] = $taskGroup->overPerformingRuns; + $result[$rec_date . '-IDs-C'] = $taskGroup->controlIntervalRuns; + $result[$rec_date . '-IDs-R'] = $taskGroup->noControlInfoRuns; } + $results[] = $result; - } - } - } + } // foreach($nodeCountData as $problemSize => $problemSizeData) { + } // foreach($runData as $appKernel => $nodeCountData) { + } // foreach($runsStatus as $resource => $runData) { + $response['success'] = true; $response['response'] = $results; $response['count'] = count($response['response']); + return $response; - } + } // public function getMapForWeb() } diff --git a/classes/Rest/Controllers/AppKernelControllerProvider.php b/classes/Rest/Controllers/AppKernelControllerProvider.php index 1b35d4a..de0bef0 100644 --- a/classes/Rest/Controllers/AppKernelControllerProvider.php +++ b/classes/Rest/Controllers/AppKernelControllerProvider.php @@ -1,16 +1,19 @@ prefix; - - $controller->get("$root/details", '\Rest\Controllers\AppKernelControllerProvider::getDetails'); - $controller->get("$root/datasets", '\Rest\Controllers\AppKernelControllerProvider::getDatasets'); - $controller->get("$root/plots", '\Rest\Controllers\AppKernelControllerProvider::getPlots'); - $controller->get("$root/control_regions", '\Rest\Controllers\AppKernelControllerProvider::getControlRegions'); - $controller->post("$root/control_regions", '\Rest\Controllers\AppKernelControllerProvider::createOrUpdateControlRegions') - ->value('update', false); - $controller->put("$root/control_regions", '\Rest\Controllers\AppKernelControllerProvider::createOrUpdateControlRegions') - ->value('update', true); - $controller->delete("$root/control_regions", '\Rest\Controllers\AppKernelControllerProvider::deleteControlRegions'); - - $controller->get("$root/notifications", '\Rest\Controllers\AppKernelControllerProvider::getNotifications'); - $controller->put("$root/notifications", '\Rest\Controllers\AppKernelControllerProvider::putNotifications'); - $controller->get("$root/notifications/default", '\Rest\Controllers\AppKernelControllerProvider::getDefaultNotifications'); - $controller->get("$root/notifications/send", '\Rest\Controllers\AppKernelControllerProvider::sendNotification'); - - $controller->get("$root/resources", '\Rest\Controllers\AppKernelControllerProvider::getResources'); - $controller->get("$root/app_kernels", '\Rest\Controllers\AppKernelControllerProvider::getAppKernels'); - - $controller->get("$root/performance_map", '\Rest\Controllers\AppKernelControllerProvider::getPerformanceMap'); - $controller->get("$root/success_rate", '\Rest\Controllers\AppKernelControllerProvider::getAppKernelSuccessRate'); + $class = get_class($this); + + $controller->get("$root/details", "$class::getDetails"); + $controller->get("$root/datasets", "$class::getDatasets"); + $controller->get("$root/plots", "$class::getPlots"); + $controller->get("$root/control_regions", "$class::getControlRegions"); + $controller->post("$root/control_regions", "$class::createOrUpdateControlRegions") + ->value("update", false); + $controller->put("$root/control_regions", "$class::createOrUpdateControlRegions") + ->value("update", true); + $controller->delete("$root/control_regions", "$class::deleteControlRegions"); + + $controller->get("$root/notifications", "$class::getNotifications"); + $controller->put("$root/notifications", "$class::putNotifications"); + $controller->get("$root/notifications/default", "$class::getDefaultNotifications"); + $controller->get("$root/notifications/send", "$class::sendNotification"); + + $controller->get("$root/resources", "$class::getResources"); + $controller->get("$root/app_kernels", "$class::getAppKernels"); + + $controller->get("$root/performance_map", "$class::getPerformanceMap"); + $controller->get("$root/success_rate", "$class::getAppKernelSuccessRate"); + + $controller->get("$root/performance_map/raw", "$class::getRawPerformanceMap"); } /** @@ -66,9 +74,9 @@ public function setupRoutes(Application $app, \Silex\ControllerCollection $contr * * Ported from: classes/REST/Appkernel/Explorer.php * - * @param Request $request The request used to make this call. - * @param Application $app The router application. - * @return array Response data containing the following info: + * @param Request $request The request used to make this call. + * @param Application $app The router application. + * @return Response Response data containing the following info: * success: A boolean indicating if the call was successful. * results: The requested information. * @throws Exception @@ -217,14 +225,15 @@ public function getDetails(Request $request, Application $app) * * Ported from: classes/REST/Appkernel/Explorer.php * - * @param Request $request The request used to make this call. - * @param Application $app The router application. - * @param boolean $returnRawData (Optional) If true, returns the data + * @param Request $request The request used to make this call. + * @param Application $app The router application. + * @param boolean $returnRawData (Optional) If true, returns the data * without converting it to a format. * (Defaults to false.) * @return array Response data containing the following info: * success: A boolean indicating if the call was successful. * results: The requested datasets. + * @throws Exception */ public function getDatasets(Request $request, Application $app, $returnRawData = false) { @@ -299,11 +308,9 @@ public function getDatasets(Request $request, Application $app, $returnRawData = { } elseif ($format == 'xls' || $format == 'csv' || $format == 'xml') { - $title = 'data'; $exportedDatas = array(); foreach ($datasetList as $result) { $exportedDatas[] = $result->export(); - $title = $result->akName . ': ' . $result->resourceName . ': ' . $result->metric; } $content = \DataWarehouse\ExportBuilder::export($exportedDatas, $format, $inline); @@ -317,15 +324,16 @@ public function getDatasets(Request $request, Application $app, $returnRawData = * * Ported from: classes/REST/Appkernel/Explorer.php * - * @param Request $request The request used to make this call. - * @param Application $app The router application. - * @return array Response data containing the following info: + * @param Request $request The request used to make this call. + * @param Application $app The router application. + * + * @return Response response data containing the following info: * success: A boolean indicating if the call was successful. * results: The requested plots. + * @throws Exception */ public function getPlots(Request $request, Application $app) { - $thumbnail = $this->getBooleanParam($request, 'thumbnail', false, false); $show_title = $this->getBooleanParam($request, 'show_title', false, false); $width = $this->getFloatParam($request, 'width', false, 740); $height = $this->getFloatParam($request, 'height', false, 345); @@ -336,6 +344,7 @@ public function getPlots(Request $request, Application $app) false, true ); + $end_date = $this->convertDateTime( $this->getDateTimeFromUnixParam($request, 'end_time'), true, @@ -351,11 +360,12 @@ public function getPlots(Request $request, Application $app) if ($legend_location === null || $legend_location == '') { $legend_location = 'bottom_center'; } + $font_size = $this->getStringParam($request, 'font_size'); if ($font_size === null || $font_size == '') { $font_size = 'default'; } - $show_guide_lines = $this->getBooleanParam($request, 'show_guide_lines', false, true); + $inline = $this->getBooleanParam($request, 'inline', false, true); $show_change_indicator = $this->getBooleanParam($request, 'show_change_indicator', false, false); $show_control_plot = $this->getBooleanParam($request, 'show_control_plot', false, false); @@ -396,7 +406,6 @@ public function getPlots(Request $request, Application $app) $chartPool = new \XDChartPool($user); } - $resourceDescription = ''; $lastResult = new \AppKernel\Dataset('Empty App Kernel Dataset', -1, "", -1, "", -1, "", "", "", ""); $hc = new \DataWarehouse\Visualization\HighChartAppKernel($start_date, $end_date, $scale, $width, $height, $user, $swap_xy); $hc->setTitle($show_title ? 'Empty App Kernel Dataset' : null, $font_size); @@ -477,7 +486,6 @@ public function getPlots(Request $request, Application $app) } } - $resourceDescription = $result->resourceDescription; if ($format != 'params') { $datasets = array($result); @@ -572,9 +580,9 @@ public function getPlots(Request $request, Application $app) * * Ported from: classes/REST/Appkernel/Explorer.php * - * @param Request $request The request used to make this call. - * @param Application $app The router application. - * @return array Response data containing the following info: + * @param Request $request The request used to make this call. + * @param Application $app The router application. + * @return JsonResponse Response data containing the following info: * success: A boolean indicating if the call was successful. * results: The requested control regions. * count: The number of control regions. @@ -599,11 +607,11 @@ public function getControlRegions(Request $request, Application $app) * * Ported from: classes/REST/Appkernel/Explorer.php * - * @param Request $request The request used to make this call. - * @param Application $app The router application. + * @param Request $request The request used to make this call. + * @param Application $app The router application. * @param boolean $update True if updating control regions. False if * creating control regions. - * @return array Response data containing the following info: + * @return JsonResponse Response data containing the following info: * success: A boolean indicating if the call was successful. * message: A human-readable message about what occurred. */ @@ -663,9 +671,9 @@ public function createOrUpdateControlRegions(Request $request, Application $app, * * Ported from: classes/REST/Appkernel/Explorer.php * - * @param Request $request The request used to make this call. - * @param Application $app The router application. - * @return array Response data containing the following info: + * @param Request $request The request used to make this call. + * @param Application $app The router application. + * @return JsonResponse Response data containing the following info: * success: A boolean indicating if the call was successful. * message: A human-readable message about what occurred. */ @@ -712,8 +720,7 @@ public function deleteControlRegions(Request $request, Application $app) /** * Retrieve information about e-mail notifications * - * @param ? - * @return array Response data containing the following info: + * @return JsonResponse Response data containing the following info: * success: A boolean indicating if the call was successful. * results: The requested information. */ @@ -759,8 +766,7 @@ public function getNotifications(Request $request, Application $app) /** * Save information about e-mail notifications * - * @param ? - * @return array Response data containing the following info: + * @return JsonResponse Response data containing the following info: * success: A boolean indicating if the call was successful. */ public function putNotifications(Request $request, Application $app) @@ -787,7 +793,7 @@ public function putNotifications(Request $request, Application $app) ); if (count($sqlres) == 0) { - $sqlres = $pdo->insert( + $pdo->insert( 'INSERT INTO mod_appkernel.report (user_id,send_report_daily,send_report_weekly,send_report_monthly,settings) VALUES (:user_id,:send_report_daily,:send_report_weekly,:send_report_monthly,:settings)', array( @@ -799,7 +805,7 @@ public function putNotifications(Request $request, Application $app) ) ); } else { - $sqlres = $pdo->execute( + $pdo->execute( 'UPDATE mod_appkernel.report SET send_report_daily=:send_report_daily,send_report_weekly=:send_report_weekly, send_report_monthly=:send_report_monthly,settings=:settings @@ -825,6 +831,11 @@ public function putNotifications(Request $request, Application $app) /** * Get DefaultNotifications settings + * + * @param Request $request + * @param Application $app + * + * @return JsonResponse */ public function getDefaultNotifications(Request $request, Application $app) { @@ -853,8 +864,9 @@ public function getDefaultNotifications(Request $request, Application $app) /** * Send e-mail report * - * @param ? - * @return array Response data containing the following info: + * @param Request $request + * @param Application $app + * @return JsonResponse|Response Response data containing the following info: * success: A boolean indicating if the call was successful. * results: The requested information. */ @@ -955,8 +967,9 @@ public function getPerformanceMap(Request $request, Application $app) /** * Send e-mail report * - * @param ? - * @return array Response data containing the following info: + * @param Request $request + * @param Application $app + * @return JsonResponse Response data containing the following info: * success: A boolean indicating if the call was successful. * results: The requested information. */ @@ -984,9 +997,6 @@ public function sendNotification(Request $request, Application $app) $report_param = json_decode($report_param, true); formatNotificationSettingsFromClient($report_param); - //print_r($report_param); - //throw new Exception(print_r($report_param,true)); - $report = new Report(array( 'start_date' => $start_date, @@ -994,10 +1004,6 @@ public function sendNotification(Request $request, Application $app) 'report_type' => $report_type, 'report_params' => $report_param, 'user' => $user - //'resource'=>$report_param['resourcesList'], - //'appKer'=>$report_param['appkernelsList'], - //'controlThresholdCoeff'=>$report_param['controlThresholdCoeff'] - //'report_param'=>$_REQUEST['report_param'] )); try { @@ -1015,48 +1021,13 @@ public function sendNotification(Request $request, Application $app) } return $app->json($response); - echo json_encode($response); - - try { - $response = array(); - $pdo = \CCR\DB::factory('database'); - - $curent_tmp_settings = $this - Param($request, 'report_param', true); - var_dump($curent_tmp_settings); - //$curent_tmp_settings=json_decode($curent_tmp_settings,true); - - $user_id = $this->getUserFromRequest($request)->getUserID(); - - formatNotificationSettingsFromClient($curent_tmp_settings, true); - - $sqlres = $pdo->query( - 'SELECT user_id,send_report_daily,send_report_weekly,send_report_monthly,settings - FROM mod_appkernel.report - WHERE user_id=:user_id', - array(':user_id' => $user_id) - ); - - if (count($sqlres) == 1) { - $sqlres = $sqlres[0]; - $settings = json_decode($sqlres['settings'], true); - foreach ($settings as $key => $value) { - $curent_tmp_settings[$key] = $value; - } - } else { - throw new Exception('settings is not set in db use default'); - } - formatNotificationSettingsForClient($curent_tmp_settings); - $response['data'] = $curent_tmp_settings; - $response['success'] = true; - return $app->json($response); - } catch (Exception $e) { - //i.e. setting is not saved by user so send defaults - return $this->getDefaultNotifications($request, $app); - } } /** * Get list of resources active in last 90 days + * @param Request $request + * @param Application $app + * @return JsonResponse */ public function getResources(Request $request, Application $app) { @@ -1086,8 +1057,6 @@ public function getResources(Request $request, Application $app) 'id' => $resource->id, 'fullname' => $resource->name, 'name' => $resource->nickname - /*'disabled' => !isset($resources[$resource->nickname]), - 'checked' => in_array($resource->id,$selectedResourceIds)*/ ); } $response['response'] = $returnData; @@ -1103,6 +1072,9 @@ public function getResources(Request $request, Application $app) /** * Get list of app kernels active in last 90 days + * @param Request $request + * @param Application $app + * @return JsonResponse */ public function getAppKernels(Request $request, Application $app) { @@ -1110,7 +1082,6 @@ public function getAppKernels(Request $request, Application $app) try { $ak_db = new \AppKernel\AppKernelDb(); $start_ts = date_timestamp_get(date_sub(date_create(), date_interval_create_from_date_string("90 days"))); - $end_ts = date_timestamp_get(date_create()); $all_app_kernels = $ak_db->getUniqueAppKernels(); $returnData = array(); @@ -1187,11 +1158,11 @@ private function convertDateTime($dateVal, $isEndDate = false, $isYMDFormat = fa * * Ported from: classes/REST/Appkernel/Explorer.php * - * @param $type Tree node type - * @param $record Record returned from the database + * @param String $type Tree node type + * @param array $record Record returned from the database * @param boolean $resource_first (Optional) (Defaults to false.) * - * @return An object representation of the tree node + * @return Object An object representation of the tree node */ private function createTreeNode($type, $record, $resource_first = false) { @@ -1431,7 +1402,6 @@ public function getAppKernelSuccessRate(Request $req, Application $app) ); } $results[$resource][$appKer][$problemSize]["unsucc"] = (int)$row['total_tasks']; - //print "\tproblemSize:".$problemSize."\n"; } //Merge results to respond $results2 = array(); @@ -1477,7 +1447,6 @@ public function getAppKernelSuccessRate(Request $req, Application $app) $icount += 1; } $unsuccessfull_tasks = $unsuccessfull_tasks . '
'; - //var_dump($sqlres); } else { $unsuccessfull_tasks = $unsuccessfull_tasks . 'There is no unsuccessful runs.
'; } @@ -1522,9 +1491,6 @@ public function getAppKernelSuccessRate(Request $req, Application $app) $succ += $row["succ"]; } - //var_dump($problemSizes); - //var_dump($resultsTMP); - foreach ($problemSizes as $problemSize) { if (array_key_exists($problemSize, $resultsTMP)) { $results2[] = $resultsTMP[$problemSize]; @@ -1614,4 +1580,116 @@ public function getAppKernelSuccessRate(Request $req, Application $app) } } } + + /** + * Retrieves the raw numeric values for the AppKernel Performance Map. This endpoint provides + * the data for `CenterReportCardPortlet.js` + * + * **NOTE:** This function will throw an UnauthorizedException if the user making the request + * does not have the Center Director or Center Staff acl. + * + * @param Request $request + * @param Application $app + * @return JsonResponse + * @throws Exception if there is a problem instantiating \DateTime objects. + * @throws Exception if the user making the request is not a Center [Director|Staff] + */ + public function getRawPerformanceMap(Request $request, Application $app) + { + $user = $this->authorize($request); + + // We need to ensure that only Center Director / Center Staff users are authorized to + // utilize this endpoint. Note, we do not utilize the `requirements` parameter of the above + // `authorize` call because it utilizes `XDUser::hasAcls` which only checks if the user has + // *all* of the supplied acls, not any of the supplied acls. + if ( ! ( $user->hasAcl(ROLE_ID_CENTER_DIRECTOR) || $user->hasAcl(ROLE_ID_CENTER_STAFF) ) ) { + throw new UnauthorizedHttpException('xdmod', "Unable to complete action. User is not authorized."); + } + + $startDate = $this->getStringParam($request, 'start_date', true); + if ($startDate !== null) { + $startDate = new \DateTime($startDate); + } + + $endDate = $this->getStringParam($request, 'end_date', true); + if ($endDate !== null) { + $endDate = new \DateTime($endDate); + } + + $appKernels = $this->getStringParam($request, 'app_kernels', false); + if (strpos($appKernels, self::DEFAULT_DELIM) !== false) { + $appKernels = explode(self::DEFAULT_DELIM, $appKernels); + } + + $problemSizes = $this->getStringParam($request, 'problem_sizes', false); + if (strpos($problemSizes, self::DEFAULT_DELIM) !== false) { + $problemSizes = explode(self::DEFAULT_DELIM, $problemSizes); + } + + $data = array(); + try { + $perfMap = new \AppKernel\PerformanceMap(array( + 'start_date' => $startDate, + 'end_date' => $endDate, + 'resource' => array('data' => $user->getResources()), + 'appKer' => $appKernels, + 'problemSize' => $problemSizes + )); + + // The columns that we're going to be retrieving from the PerformanceMap and ultimately + // returning to the requester. + $valueCols = array( + 'failedRuns', + 'inControlRuns', + 'overPerformingRuns', + 'underPerformingRuns' + ); + + // Now that we have the app kernel data, iterate through and extract / sum data for presentation. + foreach($perfMap->perfMap['runsStatus'] as $resource => $runData) { + foreach($runData as $appKernel => $nodeCountData) { + + // Values that we'll be collecting / summing by node count & date. + $values = array(); + foreach($nodeCountData as $nodeCount => $byDateData) { + foreach($byDateData as $date => $runInfo) { + + // Now that we've reached the data level, initialize or add in the data + // for the columns that we're interested in. + foreach($valueCols as $valueCol) { + if (!isset($values[$valueCol])) { + $values[$valueCol] = 0; + } + $values[$valueCol] += count($runInfo->$valueCol); + } + } + } + + $data[] = array_merge( + array( + 'resource' => $resource, + 'app_kernel' => $appKernel, + ), + $values + ); + } + } + + $results = array( + 'success' => true, + 'results' => $data + ); + } catch( Exception $e) { + + // make sure that we log the exception so that we dont lose sight of it. + handle_uncaught_exception($e); + + $results = array( + 'success' => false, + 'message' => 'An unexpected error has occurred while retrieving the AppKernel Performance Map data.' + ); + } + + return $app->json($results); + } } diff --git a/configuration/assets.d/appkernels.json b/configuration/assets.d/appkernels.json index 292ca4e..867a1bd 100644 --- a/configuration/assets.d/appkernels.json +++ b/configuration/assets.d/appkernels.json @@ -2,15 +2,21 @@ "appkernels": { "portal": { "js": [ + "gui/lib/extjs/examples/ux/ColumnHeaderGroup.js", "gui/js/modules/AppKernels.js", "gui/js/modules/app_kernels/AppKernelViewer.js", "gui/js/modules/app_kernels/AppKernelExplorer.js", "gui/js/modules/app_kernels/AppKernelNotificationPanel.js", + "gui/js/modules/app_kernels/AppKerPerformanceMapPanel.js", + "gui/js/modules/summary/CenterReportCardPortlet.js", "internal_dashboard/js/AppKernel/InstancePanel.js", "internal_dashboard/js/AppKernel/InstanceWindow.js", "internal_dashboard/js/AppKernel/InstanceStore.js", "internal_dashboard/js/Arr/ErrorMessageStore.js", "internal_dashboard/js/Arr/ErrorMessagePanel.js" + ], + "css": [ + "gui/lib/extjs/examples/ux/css/ColumnHeaderGroup.css" ] }, "internal_dashboard": { @@ -22,7 +28,6 @@ "js/Arr/AppKerSuccessRateStore.js", "js/Arr/AppKerSuccessRateGrid.js", "js/Arr/AppKerSuccessRatePanel.js", - "js/Arr/AppKerPerformanceMapPanel.js", "js/Arr/StatusPanel.js", "js/Arr/ErrorMessageStore.js", "js/Arr/ErrorMessagePanel.js", diff --git a/configuration/roles.d/appkernels-center_report_card.json b/configuration/roles.d/appkernels-center_report_card.json new file mode 100644 index 0000000..29bacf9 --- /dev/null +++ b/configuration/roles.d/appkernels-center_report_card.json @@ -0,0 +1,28 @@ +{ + "+roles": { + "+cd": { + "summary_portlets": [ + { + "name": "Center Report Card", + "type": "CenterReportCardPortlet", + "region": "left", + "config": { + "timeframe": "Year to date" + } + } + ] + }, + "+cs": { + "summary_portlets": [ + { + "name": "Center Report Card", + "type": "CenterReportCardPortlet", + "region": "left", + "config": { + "timeframe": "Month to date" + } + } + ] + } + } +} diff --git a/configuration/roles.json b/configuration/roles.json index 72a59af..0d797ea 100644 --- a/configuration/roles.json +++ b/configuration/roles.json @@ -9,7 +9,8 @@ "permitted_modules": [ "app_kernel_viewer", "app_kernel_explorer", - "app_kernel_notification" + "app_kernel_notification", + "app_kernel_performance_map" ], "javascriptClass": "XDMoD.Module.AppKernels", "javascriptReference": "CCR.xdmod.ui.appKernels", diff --git a/html/.eslintrc.json b/html/.eslintrc.json deleted file mode 100644 index 11e5694..0000000 --- a/html/.eslintrc.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "env": { - "browser": true - }, - "globals": { - "AppKernel": false, - "Compliance": false, - "CCR": false, - "Ext": false, - "ga": false, - "_gaq": false, - "jQuery": false, - "Highcharts": false, - "truncateText": false, - "XDMoD": false, - "Dashboard": false, - "DashboardStore": false - } -} diff --git a/html/gui/js/modules/AppKernels.js b/html/gui/js/modules/AppKernels.js index d57c7c9..05d372a 100644 --- a/html/gui/js/modules/AppKernels.js +++ b/html/gui/js/modules/AppKernels.js @@ -7,21 +7,22 @@ * @author Nikolay Simakov * @author Steven M. Gallo: Load content on render instead of creation */ +// eslint-disable-next-line no-undef XDMoD.Module.AppKernels = function (config) { + // eslint-disable-next-line no-undef XDMoD.Module.AppKernels.superclass.constructor.call(this, config); }; /** * Add public static methods to the AppKernels class. */ - -Ext.apply(XDMoD.Module.AppKernels, { -}); +// eslint-disable-next-line no-undef +Ext.apply(XDMoD.Module.AppKernels, {}); /** * The application kernels module. */ - +// eslint-disable-next-line no-undef Ext.extend(XDMoD.Module.AppKernels, XDMoD.PortalModule, { module_id: 'app_kernels', mainTabToken: 'main_tab_panel', @@ -58,48 +59,58 @@ Ext.extend(XDMoD.Module.AppKernels, XDMoD.PortalModule, { * ========================================================================================== */ - initialize: function(panel) { + initialize: function (panel) { + // eslint-disable-next-line no-undef var delim = CCR.xdmod.ui.tokenDelimiter; - for ( var i=0; i < panel.permitted_modules.length; i++ ) { - switch ( panel.permitted_modules[i] ) { - - case 'app_kernel_viewer': - this.appKernelViewer = new XDMoD.Module.AppKernels.AppKernelViewer({ - title: 'App Kernel Viewer', - iconCls: 'line_chart', - tooltip: 'Displays data reflecting the reliability and performance of grid resources', - id: 'app_kernel_viewer', - layoutId: this.layoutId + delim + 'app_kernel_viewer' - }); - panel.groupTabPanel.add(this.appKernelViewer); - break; - - case 'app_kernel_explorer': - this.appKernelExplorer = new XDMoD.Module.AppKernels.AppKernelExplorer({ - title: 'App Kernel Explorer', - iconCls: 'line_chart', - tooltip: 'Displays data reflecting the reliability and performance of grid resources', - id: 'app_kernel_explorer' - }); - panel.groupTabPanel.add(this.appKernelExplorer); - break; - - case 'app_kernel_notification': - this.appKernelNotificationPanel = new XDMoD.Module.AppKernels.AppKernelNotificationPanel({ - id: 'app_kernel_notification', - title:'Reports' - }); - panel.groupTabPanel.add(this.appKernelNotificationPanel); - break; - - default: - break; + for (var i = 0; i < panel.permitted_modules.length; i++) { + switch (panel.permitted_modules[i]) { + + case 'app_kernel_viewer': + // eslint-disable-next-line no-undef + this.appKernelViewer = new XDMoD.Module.AppKernels.AppKernelViewer({ + title: 'App Kernel Viewer', + iconCls: 'line_chart', + tooltip: 'Displays data reflecting the reliability and performance of grid resources', + id: 'app_kernel_viewer', + layoutId: this.layoutId + delim + 'app_kernel_viewer' + }); + panel.groupTabPanel.add(this.appKernelViewer); + break; + + case 'app_kernel_explorer': + // eslint-disable-next-line no-undef + this.appKernelExplorer = new XDMoD.Module.AppKernels.AppKernelExplorer({ + title: 'App Kernel Explorer', + iconCls: 'line_chart', + tooltip: 'Displays data reflecting the reliability and performance of grid resources', + id: 'app_kernel_explorer' + }); + panel.groupTabPanel.add(this.appKernelExplorer); + break; + + case 'app_kernel_notification': + // eslint-disable-next-line no-undef + this.appKernelNotificationPanel = new XDMoD.Module.AppKernels.AppKernelNotificationPanel({ + id: 'app_kernel_notification', + title: 'Reports' + }); + panel.groupTabPanel.add(this.appKernelNotificationPanel); + break; + case 'app_kernel_performance_map': + // eslint-disable-next-line no-undef + this.appKernelPerformanceMapPanel = new XDMoD.Arr.AppKerPerformanceMapPanel({ + id: 'app_kernel_performance_map', + title: 'Performance Map' + }); + panel.groupTabPanel.add(this.appKernelPerformanceMapPanel); + break; + default: + break; } // switch ( moduleName ) } // for ( moduleName in self.permitted_modules ) panel.on('activate', panel.setSubtab, panel); - }, // initialize() /* ========================================================================================== @@ -108,64 +119,91 @@ Ext.extend(XDMoD.Module.AppKernels, XDMoD.PortalModule, { * ========================================================================================== */ - setSubtab: function(panel) { - + setSubtab: function (panel) { // The activate event is fired multuiple times but I'm not sure where it's coming from so // ignore multiple events. - if ( ! panel.first_time_activate ) return; - + if (!panel.first_time_activate) { + return; + } + + // eslint-disable-next-line no-param-reassign panel.first_time_activate = false; - }, // setSubtab() listeners: { - beforerender: function(panel) { + beforerender: function (panel) { // Initialize the contents before the tab panel is rendered. this.initialize(panel); } - }, + }, /** * Initialize app kernel module. */ initComponent: function () { + // eslint-disable-next-line no-undef var delim = CCR.xdmod.ui.tokenDelimiter; this.layoutId = this.mainTabToken + delim + this.id; this.first_time_activate = true; + // eslint-disable-next-line no-undef this.groupTabPanel = new Ext.TabPanel({ id: 'app_kernels_grouptab', activeTab: 0, region: 'center', listeners: { - 'tabchange': { + tabchange: { fn: function (tabpanel, tab) { - var viewer = CCR.xdmod.ui.Viewer.getViewer(); + // eslint-disable-next-line no-undef, no-shadow var delim = CCR.xdmod.ui.tokenDelimiter; var hist = this.mainTabToken + delim + this.module_id + delim + tab.id; - if( "current_hash" in tab && tab.current_hash !== '' ) { + if ('current_hash' in tab && tab.current_hash !== '') { hist = tab.current_hash; } - if ( this.first_time_activate === false ) { + if (this.first_time_activate === false) { + // eslint-disable-next-line no-undef Ext.History.add(hist); } }, scope: this - }, // tabchange - activate: function(panel) { - console.log("groupTabPanel activate " + panel.id); - } + } // tabchange } }); - + // eslint-disable-next-line no-undef Ext.apply(this, { - items: [this.groupTabPanel] + items: [this.groupTabPanel], + listeners: { + + /** + * This event fires when the tab has been `activated`, a.k.a + * selected by the user clicking on the tab or by having + * `app_kernels` identifier included in the url hash. Specifically + * we're looking to see if we need to activate a `subTab`. + */ + activate: function () { + if (this.subTab !== undefined) { + var currentlyActive = this.groupTabPanel.getActiveTab(); + if (currentlyActive && currentlyActive.id === this.subTab) { + // If it's the same activeTab then we need to force + // fire the 'activate' event. + currentlyActive.fireEvent('activate', currentlyActive); + } else { + this.groupTabPanel.suspendEvents(false); + this.groupTabPanel.setActiveTab(this.subTab); + this.groupTabPanel.resumeEvents(); + } + + this.subTab = undefined; + } + } + } }); - + + // eslint-disable-next-line no-undef XDMoD.Module.AppKernels.superclass.initComponent.apply(this, arguments); } // initComponent() }); diff --git a/html/gui/js/modules/app_kernels/AppKerPerformanceMapPanel.js b/html/gui/js/modules/app_kernels/AppKerPerformanceMapPanel.js new file mode 100644 index 0000000..78db055 --- /dev/null +++ b/html/gui/js/modules/app_kernels/AppKerPerformanceMapPanel.js @@ -0,0 +1,781 @@ +/* global Ext, XDMoD, CCR, document, window */ +/** + * ARR active tasks grid. + * + * @author Nikolay A. Simakov + */ +Ext.namespace('XDMoD', 'XDMoD.Arr', 'CCR', 'CCR.xdmod', 'CCR.xdmod.ui', 'Ext.ux.grid'); +Ext.QuickTips.init(); // enable tooltips + +XDMoD.Arr.AppKerPerformanceMapStore = Ext.extend(Ext.data.JsonStore, { + restful: true, + + proxy: XDMoD.REST.createHttpProxy({ + url: 'app_kernels/performance_map', + method: 'GET' + }), + constructor: function (config) { + // eslint-disable-next-line no-param-reassign + config = config || {}; + + Ext.apply(config, { + baseParams: {} + }); + + XDMoD.Arr.AppKerPerformanceMapStore.superclass.constructor.call(this, config); + } +}); + +XDMoD.Arr.AppKerPerformanceMapGrid = Ext.extend(Ext.grid.GridPanel, { + id: 'ak_perfmap', + loadMask: true, + listeners: { + viewready: function () { + this.store.load(); + } + }, + colorStyles: { + W: 'style="background-color:white;"', + F: 'style="background-color:#FFB0C4;"', + U: 'style="background-color:#F7FE2E;"', + O: 'style="background-color:#FE9A2E;"', + C: 'style="background-color:#81BEF7;"', + N: 'style="background-color:#B0FFC5;"', + R: 'style="background-color:#F781F3;"' + }, + rendererForCell: function (value) { + if (value !== ' ' && value !== '') { + var v = value.split('/'); + for (var i = 1; i < v.length; i++) { + v[i] = parseInt(v[i], 10); + } + if (v[0] in this.colorStyles) { + return '
' + value + '
'; + } + } + return value || 0; + }, + metaDataChanged: function () { + var newColumns = [{ + header: 'Resource', + dataIndex: 'resource', + width: 80 + }, { + header: 'App Kernel', + dataIndex: 'appKer', + width: 90 + + }, { + header: 'Nodes', + dataIndex: 'problemSize', + align: 'right', + width: 50 + }]; + var nLocked = newColumns.length; + /* eslint-disable block-scoped-var */ + for (var i = nLocked; i < this.store.fields.getCount(); i++) { + var value = this.store.fields.itemAt(i).name; + var m_date = value.split('/'); + if (value.indexOf('Failed') >= 0) { + continue; + } + + if (value.indexOf('InControl') >= 0) { + continue; + } + if (value.indexOf('OutOfControl') >= 0) { + continue; + } + if (value.indexOf('IDs') >= 0) { + continue; + } + + var day = m_date[2]; + + newColumns.push({ + header: day, + dataIndex: value, + align: 'center', + renderer: { + fn: this.rendererForCell, + scope: this + }, + width: 40 + }); + } + /* eslint-enable block-scoped-var */ + var newColModel = new Ext.grid.ColumnModel({ + defaults: { + sortable: false + }, + columns: newColumns + }); + + this.dateGroup[0].length = 1; + + var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + /* eslint-disable block-scoped-var, no-redeclare */ + for (var i = nLocked; i < this.store.fields.getCount(); i++) { + var value = this.store.fields.itemAt(i).name; + var m_date = value.split('/'); + if (value.indexOf('IDs') >= 0) { + continue; + } + + var monthYear = monthNames[parseInt(m_date[1], 10) - 1] + ', ' + m_date[0]; + + if (this.dateGroup[0][this.dateGroup[0].length - 1].header === monthYear) { + this.dateGroup[0][this.dateGroup[0].length - 1].colspan++; + } else { + this.dateGroup[0].push({ + header: monthYear, + colspan: 1, + align: 'center' + }); + } + } + /* eslint-enable block-scoped-var, no-redeclare */ + newColModel.rows = this.dateGroup; + + this.reconfigure(this.store, newColModel); + }, + + constructor: function (config) { + // eslint-disable-next-line no-param-reassign + config = config || {}; + + this.store = new Ext.data.JsonStore({ + baseParams: {}, + proxy: XDMoD.REST.createHttpProxy({ + url: 'app_kernels/performance_map', + method: 'GET' + }) + }); + + this.store.on('metadatachanged', this.metaDataChanged, this); + this.store.on('datachanged', this.metaDataChanged, this); + this.store.on('reconfigure', this.metaDataChanged, this); + + this.dateGroup = [[{ + header: '', + colspan: 3, + align: 'center' + }]]; + + var dateHeader = new Ext.ux.grid.ColumnHeaderGroup({ + rows: this.dateGroup + }); + this.plugins = [dateHeader]; + + Ext.apply(config, { + id: 'appKerPerformanceMapGrid', + cls: 'appKerPerfMap_grid', + columnLines: true, + enableColumnMove: false, + enableColumnHide: false, + colModel: new Ext.ux.grid.LockingColumnModel({ + + defaults: { + sortable: false + }, + columns: [{ + header: 'Resource', + dataIndex: 'resource' + }, { + header: 'App Kernel', + dataIndex: 'appKer', + width: 100 + }, { + header: 'Nodes', + dataIndex: 'problemSize', + align: 'right', + width: 60 + }] + }), + plugins: dateHeader, + selModel: new Ext.grid.CellSelectionModel({ + singleSelect: true + }) + }); + + XDMoD.Arr.AppKerPerformanceMapGrid.superclass.constructor.call(this, config); + } +}); + +XDMoD.Arr.AppKerPerformanceMapPanel = function (config) { + XDMoD.Arr.AppKerPerformanceMapPanel.superclass.constructor.call(this, config); +}; + +Ext.extend(XDMoD.Arr.AppKerPerformanceMapPanel, Ext.Panel, { + title: 'Performance Map', + resourcesList: ['blacklight', 'edge', 'edge12core', 'lonestar4', 'kraken', 'trestles', 'gordon', 'stampede'], + problemSizeList: [1, 2, 4, 8, 16], + appKerList: ['xdmod.app.astro.enzo', 'xdmod.app.chem.gamess', 'xdmod.app.chem.nwchem', 'xdmod.app.md.namd', 'xdmod.benchmark.hpcc', 'xdmod.benchmark.io.ior', 'xdmod.benchmark.io.mpi-tile-io', 'xdmod.benchmark.mpi.imb', 'xdmod.benchmark.graph.graph500', 'xdmod.bundle'], + legend: + 'Each day summarized in table cell as pair of a symbol and a number.' + + 'Symbol represent the status of last application kernel execution on that day and number' + + ' shows total number of runs. Each cell is colored according to the statu' + + 's of last application kernel run. Below is the codes description:
<' + + 'br/>Description' + + '' + + '
Code
NApplication kernel was executed within control interval
UApplication kernel w' + + 'as under-performing
OApplication kernel was over-performing
FApplication kernel failed to ru' + + 'n
CThis ru' + + 'n was used to calculate control region
RApplication kernel have run, but control i' + + 'nformation is not available
' + + 'There was no application kernel runs
' + + 'Select cell for more details', + initComponent: function () { + var appKerPerformanceMapGrid = new XDMoD.Arr.AppKerPerformanceMapGrid({ + scope: this, + region: 'center' + }); + + this.appKerPerformanceMapGrid = appKerPerformanceMapGrid; + + var resourceChildren = []; + /* eslint-disable block-scoped-var */ + for (var i = 0; i < this.resourcesList.length; i++) { + var resource = this.resourcesList[i]; + /* eslint-enable block-scoped-var */ + resourceChildren.push({ + text: resource, + nick: resource, + type: 'resource', + checked: true, + iconCls: 'resource', + leaf: true + }); + } + + this.resourcesTree = new Ext.tree.TreePanel({ + title: 'Resources', + id: 'tree_resources_' + this.id, + useArrows: true, + autoScroll: true, + animate: false, + enableDD: false, + region: 'north', + root: new Ext.tree.AsyncTreeNode({ + nodeType: 'async', + text: 'Resources', + draggable: false, + id: 'resources', + expanded: true, + children: resourceChildren + }), + rootVisible: false, + containerScroll: true, + tools: [{ + id: 'unselect', + qtip: 'De-select all selected resources.', + scope: this, + handler: function () { + // eslint-disable-next-line no-use-before-define + this.resourcesTree.un('checkchange', reloadAll, this); + var lastNode = null; + var selectAll = true; + + this.resourcesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + selectAll = false; + } + lastNode = n; + }); + + if (selectAll) { + this.resourcesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (!ui.isChecked()) { + ui.toggleCheck(true); + } + lastNode = n; + }); + } else { + this.resourcesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + ui.toggleCheck(false); + } + lastNode = n; + }); + } + if (lastNode) { + // eslint-disable-next-line no-use-before-define + reloadAll.call(this); + } + // eslint-disable-next-line no-use-before-define + this.resourcesTree.on('checkchange', reloadAll, this); + } + }, { + id: 'refresh', + qtip: 'Refresh', + hidden: true, + scope: this, + // eslint-disable-next-line no-use-before-define + handler: reloadAll + }], + margins: '0 0 0 0', + border: false, + split: true, + flex: 4 + }); + + var problemSizeChildren = []; + /* eslint-disable block-scoped-var, no-redeclare */ + for (var i = 0; i < this.problemSizeList.length; i++) { + var nodesSize = this.problemSizeList[i]; + /* eslint-enable block-scoped-var */ + problemSizeChildren.push({ + text: String(nodesSize), + qtip: (nodesSize === 1) ? nodesSize + 'node' : nodesSize + 'nodes', + type: 'node', + checked: true, + iconCls: 'node', + leaf: true + }); + } + + this.problemSizesTree = new Ext.tree.TreePanel({ + flex: 0.5, + title: 'Problem Size (Cores or Nodes)', + id: 'tree_nodes_' + this.id, + useArrows: true, + autoScroll: true, + animate: false, + enableDD: false, + + root: new Ext.tree.AsyncTreeNode({ + nodeType: 'async', + text: 'Resources', + draggable: false, + id: 'resources', + expanded: true, + children: problemSizeChildren + }), + tools: [{ + id: 'unselect', + qtip: 'De-select all selected resources.', + scope: this, + handler: function () { + // eslint-disable-next-line no-use-before-define + this.problemSizesTree.un('checkchange', reloadAll, this); + var lastNode = null; + var selectAll = true; + + this.problemSizesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + selectAll = false; + } + lastNode = n; + }); + + if (selectAll) { + this.problemSizesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (!ui.isChecked()) { + ui.toggleCheck(true); + } + lastNode = n; + }); + } else { + this.problemSizesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + ui.toggleCheck(false); + } + lastNode = n; + }); + } + + if (lastNode) { + // eslint-disable-next-line no-use-before-define + reloadAll.call(this); + } + // eslint-disable-next-line no-use-before-define + this.problemSizeTree.on('checkchange', reloadAll, this); + } + }, { + id: 'refresh', + qtip: 'Refresh', + hidden: true, + scope: this, + // eslint-disable-next-line no-use-before-define + handler: reloadAll + }], + rootVisible: false, + containerScroll: true, + margins: '0 0 0 0', + border: false + }); + + var appKerChildren = []; + /* eslint-disable block-scoped-var, no-redeclare */ + for (var i = 0; i < this.appKerList.length; i++) { + var appker = this.appKerList[i]; + /* eslint-enable block-scoped-var */ + appKerChildren.push({ + text: appker, + nick: appker, + type: 'app_kernel', + checked: true, + iconCls: 'appkernel', + leaf: true + }); + } + + this.appKerTree = new Ext.tree.TreePanel({ + title: 'App Kernels', + id: 'tree_appker_' + this.id, + useArrows: true, + autoScroll: true, + animate: false, + enableDD: false, + region: 'north', + root: new Ext.tree.AsyncTreeNode({ + nodeType: 'async', + text: 'App Kernels', + draggable: false, + id: 'appker', + expanded: true, + children: appKerChildren + }), + tools: [{ + id: 'unselect', + qtip: 'De-select all selected resources.', + scope: this, + handler: function () { + // eslint-disable-next-line no-use-before-define + this.appKerTree.un('checkchange', reloadAll, this); + var lastNode = null; + var selectAll = true; + + this.appKerTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + selectAll = false; + } + lastNode = n; + }); + + if (selectAll) { + this.appKerTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (!ui.isChecked()) { + ui.toggleCheck(true); + } + lastNode = n; + }); + } else { + this.appKerTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + ui.toggleCheck(false); + } + lastNode = n; + }); + } + + if (lastNode) { + // eslint-disable-next-line no-use-before-define + reloadAll.call(this); + } + // eslint-disable-next-line no-use-before-define + this.appKerTree.on('checkchange', reloadAll, this); + } + }, { + id: 'refresh', + qtip: 'Refresh', + hidden: true, + scope: this, + // eslint-disable-next-line no-use-before-define + handler: reloadAll + }], + rootVisible: false, + containerScroll: true, + margins: '0 0 0 0', + border: false, + split: true, + flex: 4 + }); + + var commentsTemplate = new Ext.XTemplate( + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '
', + 'Resource: {resource}
', + '
', + 'Application Kernel: {appKer}
', + '
', + ' Failed runs: {failedJobs}
', + '
', + ' Under-performing runs: {underPerformingJobs}
', + '
', + ' Over-performing runs: {overPerformingJobs}
', + '
', + ' In control runs: {inControlJobs}
', + '
', + ' Runs used for control calculations: {controlJobs}
', + '
', + ' Runs without control information: {noControlInfoJobs}
', + '

', + this.legend + ); + this.commentsPanel = new Ext.Panel({ + id: 'commentsPanel', + region: 'south', + autoScroll: true, + border: true, + collapsible: true, + split: true, + title: 'Description', + height: 130, + html: this.legend + }); // commentsPanel + + this.appKerPerformanceMapGrid.getSelectionModel().on('cellselect', function (sm, rowIdx, colIndex) { + /* populate detailed view pannele with arr job ids */ + // eslint-disable-next-line no-shadow + var i; + var j; + + var detailPanel = Ext.getCmp('commentsPanel'); + var dataIndex = sm.grid.getColumnModel().getDataIndex(colIndex); + var record = sm.grid.getStore().getAt(rowIdx); + + var dataIndexAll = [dataIndex]; + // if columns with resource or appkernel name is selected show all jobs for the queried period + if (colIndex <= 2) { + dataIndexAll = []; + for (j = 3; j < record.fields.getCount(); j++) { + var key = record.fields.getKey(record.fields.itemAt(j)); + if (key.indexOf('Failed') >= 0) { + continue; + } + if (key.indexOf('InControl') >= 0) { + continue; + } + if (key.indexOf('OutOfControl') >= 0) { + continue; + } + dataIndexAll.push(key); + } + } + // pack jobs + var iStatus; + var statuses = ['F', 'U', 'N', 'O', 'C', 'R']; + var ref = { + F: 'failedJobs', + U: 'underPerformingJobs', + N: 'inControlJobs', + O: 'overPerformingJobs', + C: 'controlJobs', + R: 'noControlInfoJobs' + }; + var jobsIDs = { + F: '', + U: '', + N: '', + O: '', + C: '', + R: '' + }; + + for (j = dataIndexAll.length - 1; j >= 0; j--) { + var dataIndexRun = dataIndexAll[j]; + var s; + for (iStatus = 0; iStatus < statuses.length; iStatus++) { + s = record.get(dataIndexRun + '-IDs-' + statuses[iStatus]); + if (typeof s !== 'undefined' && s !== '' && s !== ' ') { + var runs = s.split(','); + for (i = 0; i < runs.length; i++) { + runs[i] = parseInt(runs[i], 10); + } + runs.sort(function (a, b) { + return b - a; + }); + for (i = 0; i < runs.length; i++) { + if (jobsIDs[statuses[iStatus]] !== '') { + jobsIDs[statuses[iStatus]] += ', '; + } + jobsIDs[statuses[iStatus]] += '
' + runs[i] + ''; + } + } + } + } + var dataValue = record.get(dataIndex); + var values = { + appKer: record.get('appKer'), + resource: record.get('resource'), + rowIdx: rowIdx, + colIndex: colIndex, + dataIndex: dataIndex, + dataValue: dataValue + }; + for (iStatus = 0; iStatus < statuses.length; iStatus++) { + values[ref[statuses[iStatus]]] = jobsIDs[statuses[iStatus]]; + } + commentsTemplate.overwrite(detailPanel.body, values); + }); + + var viewPanel = new Ext.Panel({ + layout: 'border', + region: 'center', + items: [this.appKerPerformanceMapGrid, this.commentsPanel], + border: true + }); // viewPanel + + this.durationToolbar = new CCR.xdmod.ui.DurationToolbar({ + id: 'duration_selector_' + this.id, + alignRight: false, + showRefresh: true, + showAggregationUnit: false, + handler: function () { + // eslint-disable-next-line no-use-before-define + reloadAll.call(this); + }, + scope: this // also scope of handle + }); + + this.durationToolbar.dateSlider.region = 'south'; + + function exportFunction(format) { + var parameters = appKerPerformanceMapGrid.store.baseParams; + + parameters.format = format; + + CCR.invokePost('controllers/arr_controller.php', parameters, { + checkDashboardUser: true + }); + } + var exportButton = new Ext.Button({ + id: 'export_button_' + this.id, + text: 'Export', + iconCls: 'export', + tooltip: 'Export chart data', + menu: [{ + text: 'CSV - comma Separated Values', + iconCls: 'csv', + handler: function () { + exportFunction('csv', false); + } + }] + }); + this.durationToolbar.addItem('-'); + this.durationToolbar.addItem(exportButton); + + var getBaseParams = function () { + return { + start_date: this.durationToolbar.getStartDate().format('Y-m-d'), + end_date: this.durationToolbar.getEndDate().format('Y-m-d'), + format: 'json' + }; + }; + + this.appKerPerformanceMapGrid.store.on('beforeload', function () { + if (!this.durationToolbar.validate()) { + return; + } + + var baseParams = {}; + Ext.apply(baseParams, getBaseParams.call(this)); + + this.appKerPerformanceMapGrid.store.baseParams = baseParams; + }, this); + + this.appKerPerformanceMapGrid.store.on('load', function () { + if (this.resource && this.app_kernel) { + var index = this.appKerPerformanceMapGrid.store.findBy(function (record) { + return record.get('resource') === this.resource && this.app_kernel.indexOf(record.get('appKer').toLowerCase()) !== -1; + }, this); + + if (index >= 0) { + this.appKerPerformanceMapGrid.getSelectionModel().select(index, 0); + + this.resource = undefined; + this.app_kernel = undefined; + } + } + + // Ensure that we unmask the main interface once we're done loading. + var viewer = CCR.xdmod.ui.Viewer.getViewer(); + if (viewer.el) { + viewer.el.unmask(); + } + }, this); + + function reloadAll() { + this.appKerPerformanceMapGrid.store.load(); + } + + Ext.apply(this, { + layout: 'border', + tbar: this.durationToolbar, + items: [viewPanel], + listeners: { + activate: function () { + var token = CCR.tokenize(document.location.hash); + var params = Ext.urlDecode(token.params); + + if (params.ak) { + var info = Ext.decode(window.atob(params.ak)); + + if (info.resource) { + this.resource = info.resource; + } + + if (info.app_kernel) { + this.app_kernel = info.app_kernel; + } + + var refresh = false; + if (info.start_date) { + this.durationToolbar.startDateField.setValue(info.start_date); + refresh = true; + } + if (info.end_date) { + this.durationToolbar.endDateField.setValue(info.end_date); + refresh = true; + } + + if (refresh) { + this.durationToolbar.onHandle(true); + } + } + } + } + }); // Ext.apply + + XDMoD.Arr.AppKerPerformanceMapPanel.superclass.initComponent.apply(this, arguments); + } // initComponent +}); +// XDMoD.Arr.AppKerPerformanceMapPanel diff --git a/html/gui/js/modules/app_kernels/AppKernelExplorer.js b/html/gui/js/modules/app_kernels/AppKernelExplorer.js index 212f490..c6a7fc9 100644 --- a/html/gui/js/modules/app_kernels/AppKernelExplorer.js +++ b/html/gui/js/modules/app_kernels/AppKernelExplorer.js @@ -10,16 +10,12 @@ * Contains the code for the App Kernel Explorer * */ - +/* global XDMoD, Ext, CCR */ XDMoD.Module.AppKernels.AppKernelExplorer = function (config) { - XDMoD.Module.AppKernels.AppKernelExplorer.superclass.constructor.call(this, config); - }; -// =========================================================================== - Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { module_id: 'data_explorer', @@ -36,7 +32,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { showAggregationUnit: false } - }, //durationSelector + }, // durationSelector exportMenu: true, printButton: true, @@ -48,101 +44,83 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { font_size: 3, swap_xy: false, - // ------------------------------------------------------------------ - getSelectedResourceIds: function () { - var resources = []; var selNodes = this.resourcesTree.getChecked(); Ext.each(selNodes, function (node) { - if (!node.disabled) resources.push(node.id); + if (!node.disabled) { + resources.push(node.id); + } }); return resources; - - }, //getSelectedResourceIds - - // ------------------------------------------------------------------ + }, // getSelectedResourceIds getSelectedPUCounts: function () { - var nodes = []; var selNodes = this.pusTree.getChecked(); Ext.each(selNodes, function (node) { - if (!node.disabled) nodes.push(node.id); + if (!node.disabled) { + nodes.push(node.id); + } }); return nodes; - - }, //getSelectedPUCounts - - // ------------------------------------------------------------------ + }, // getSelectedPUCounts getExpandedAppKernels: function () { - var aks = []; this.metricsTree.root.cascade(function (node) { - if (node.isExpanded()) { aks.push(node.id); } - }, this); return aks; - - }, //getExpandedAppKernels - - // ------------------------------------------------------------------ + }, // getExpandedAppKernels getSelectedMetrics: function () { - var metrics = []; var selNodes = this.metricsTree.getChecked(); Ext.each(selNodes, function (node) { - if (!node.disabled) metrics.push(node.id); + if (!node.disabled) { + metrics.push(node.id); + } }); return metrics; - - }, //getSelectedMetrics - - // ------------------------------------------------------------------ + }, // getSelectedMetrics initComponent: function () { - var self = this; var chartScale = 1; - var chartThumbScale = 0.45; var chartWidth = 740; var chartHeight = 345; - // --------------------------------------------------------- - self.on('duration_change', function (d) { var start_time = self.getDurationSelector().getStartDate() / 1000.0; var end_time = self.getDurationSelector().getEndDate() / 1000.0; self.metricsTree.getRootNode().cascade(function (n) { var enabled = (start_time <= n.attributes.end_ts && n.attributes.end_ts <= end_time) || (n.attributes.start_ts <= end_time && end_time <= n.attributes.end_ts); - if (enabled) n.enable(); - else n.disable(); - //if(!enabled)n.setText(n.text+'*'); - + if (enabled) { + n.enable(); + } else { + n.disable(); + } return true; }); + /* eslint-disable no-use-before-define */ reloadResources.call(this); reloadChart.call(this, 150); - - }); //self.on('duration_change', ... - - // --------------------------------------------------------- + /* eslint-enable no-use-before-define */ + }); // self.on('duration_change', ... var resourcesStore = new CCR.xdmod.CustomJsonStore({ @@ -161,10 +139,9 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { listeners: { - 'load': { + load: { fn: function (tstore) { - if (tstore.getCount() <= 0) { return; } @@ -177,32 +154,25 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { id: 'resources', children: Ext.util.JSON.decode(tstore.getAt(0).get('nodes')) - }); //root + }); // root this.resourcesTree.setRootNode(root); this.resourcesTree.render(); root.expand(); - - }, //fn - + }, // fn scope: this - - } //load - - } //listeners - - }); //resourcesStore - - // --------------------------------------------------------- + } // load + } // listeners + }); // resourcesStore var getBaseParams = function () { - var selectedResourceIds = this.getSelectedResourceIds(); var selectedPUCounts = this.getSelectedPUCounts(); var selectedMetrics = this.getSelectedMetrics(); var expandedAppKernels = this.getExpandedAppKernels(); var baseParams = {}; + // eslint-disable-next-line no-use-before-define baseParams.show_change_indicator = toggleChangeIndicator.pressed ? 'y' : 'n'; baseParams.show_title = 'n'; baseParams.start_date = self.getDurationSelector().getStartDate().format('Y-m-d'); @@ -219,32 +189,25 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { baseParams.show_title = 'y'; return baseParams; - - }; //getBaseParams - - // --------------------------------------------------------- + }; // getBaseParams var reloadResources = function () { - Ext.apply(resourcesStore.baseParams, getBaseParams.call(this)); resourcesStore.load(); - - }; //reloadResources - - // --------------------------------------------------------- + }; // reloadResources var suppressResourceCheckTrackCall = false; var resoucesTreeCheckChange = function (node, checked) { - - if (suppressResourceCheckTrackCall == false) - XDMoD.TrackEvent('App Kernel Explorer', 'Clicked a checkbox in the Resources tree', Ext.encode({item: node.getPath('text'), checked: checked})); - + if (suppressResourceCheckTrackCall === false) { + XDMoD.TrackEvent('App Kernel Explorer', 'Clicked a checkbox in the Resources tree', Ext.encode({ + item: node.getPath('text'), + checked: checked + })); + } + // eslint-disable-next-line no-use-before-define reloadChart.call(this); - - }; //resoucesTreeCheckChange - - // --------------------------------------------------------- + }; // resoucesTreeCheckChange this.resourcesTree = new Ext.tree.TreePanel({ @@ -255,7 +218,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { animate: false, enableDD: false, region: 'north', - //height: 200, + // height: 200, root: { nodeType: 'async', @@ -276,65 +239,65 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { scope: this, handler: function () { - XDMoD.TrackEvent('App Kernel Explorer', 'Clicked on the uncheck all tool in the Resources tree'); suppressResourceCheckTrackCall = true; this.resourcesTree.un('checkchange', resoucesTreeCheckChange, this); var lastNode = null; - var selectAll = true; - this.resourcesTree.getRootNode().cascade(function(n) { + var selectAll = true; + this.resourcesTree.getRootNode().cascade(function (n) { var ui = n.getUI(); - if(ui.isChecked()) selectAll=false; + if (ui.isChecked()) { + selectAll = false; + } lastNode = n; - }); - - if(selectAll){ - XDMoD.TrackEvent('App Kernel Explorer', 'Checking all items in the Resources tree'); - this.resourcesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if(!ui.isChecked()) ui.toggleCheck(true); - lastNode = n; + }); + + if (selectAll) { + XDMoD.TrackEvent('App Kernel Explorer', 'Checking all items in the Resources tree'); + this.resourcesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (!ui.isChecked()) { + ui.toggleCheck(true); + } + lastNode = n; }); - } - else{ - XDMoD.TrackEvent('App Kernel Explorer', 'Clearing all checked items in the Resources tree'); - this.resourcesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if(ui.isChecked()) ui.toggleCheck(false); - lastNode = n; + } else { + XDMoD.TrackEvent('App Kernel Explorer', 'Clearing all checked items in the Resources tree'); + this.resourcesTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + ui.toggleCheck(false); + } + lastNode = n; }); - } + } - if (lastNode) resoucesTreeCheckChange.call(this, lastNode, false); + if (lastNode) { + resoucesTreeCheckChange.call(this, lastNode, false); + } this.resourcesTree.on('checkchange', resoucesTreeCheckChange, this); suppressResourceCheckTrackCall = false; - - } //handler + } // handler }, { - id: 'refresh', qtip: 'Refresh', hidden: true, scope: this, handler: reloadResources - } - ], //tools + ], // tools margins: '0 0 0 0', border: false, split: true, flex: 1.5 - - }); //this.resourcesTree - - // --------------------------------------------------------- + }); // this.resourcesTree this.resourcesTree.on('checkchange', resoucesTreeCheckChange, this); @@ -355,10 +318,9 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { listeners: { - 'load': { + load: { fn: function (tstore) { - if (tstore.getCount() <= 0) { return; } @@ -371,53 +333,43 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { id: 'nodes', children: Ext.util.JSON.decode(tstore.getAt(0).get('nodes')) - }); //root + }); // root this.pusTree.setRootNode(root); this.pusTree.render(); root.expand(); - - }, //fn + }, // fn scope: this - - } //load - - } //listeners - - }); //pusStore - - // --------------------------------------------------------- + } // load + } // listeners + }); // pusStore var reloadPUs = function () { - Ext.apply(pusStore.baseParams, getBaseParams.call(this)); pusStore.load(); - - }; //reloadPUs - - // --------------------------------------------------------- + }; // reloadPUs var suppressPUsCheckTrackCall = false; var pusTreeCheckChange = function (node, checked) { - - if (suppressPUsCheckTrackCall == false) - XDMoD.TrackEvent('App Kernel Explorer', 'Clicked a checkbox in the Processing Units tree', Ext.encode({item: node.getPath('text'), checked: checked})); + if (suppressPUsCheckTrackCall === false) { + XDMoD.TrackEvent('App Kernel Explorer', 'Clicked a checkbox in the Processing Units tree', Ext.encode({ + item: node.getPath('text'), + checked: checked + })); + } reloadResources.call(this); - //reloadMetrics.call(this); + // eslint-disable-next-line no-use-before-define reloadChart.call(this); - - }; //pusTreeCheckChange - - // --------------------------------------------------------- + }; // pusTreeCheckChange this.pusTree = new Ext.tree.TreePanel({ flex: 1, - title: "Processing Units", + title: 'Processing Units', id: 'tree_pus_' + this.id, useArrows: true, autoScroll: true, @@ -443,74 +395,75 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { scope: this, handler: function () { - XDMoD.TrackEvent('App Kernel Explorer', 'Clicked on the uncheck all tool in the Processing Units tree'); suppressPUsCheckTrackCall = true; this.pusTree.un('checkchange', pusTreeCheckChange, this); var lastNode = null; - var selectAll = true; + var selectAll = true; - this.pusTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if(ui.isChecked()) selectAll=false; - lastNode = n; - }); + this.pusTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + selectAll = false; + } + lastNode = n; + }); - if(selectAll){ - XDMoD.TrackEvent('App Kernel Explorer', 'Checking all items in the Processing Units tree'); - this.pusTree.getRootNode().cascade(function(n) { + if (selectAll) { + XDMoD.TrackEvent('App Kernel Explorer', 'Checking all items in the Processing Units tree'); + this.pusTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (!ui.isChecked()) { + ui.toggleCheck(true); + } + lastNode = n; + }); + } else { + XDMoD.TrackEvent('App Kernel Explorer', 'Clearing all checked items in the Processing Units tree'); + this.pusTree.getRootNode().cascade(function (n) { var ui = n.getUI(); - if(!ui.isChecked()) ui.toggleCheck(true); + if (ui.isChecked()) { + ui.toggleCheck(false); + } lastNode = n; - }); - } - else{ - XDMoD.TrackEvent('App Kernel Explorer', 'Clearing all checked items in the Processing Units tree'); - this.pusTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if(ui.isChecked()) ui.toggleCheck(false); - lastNode = n; - }); - } - - if (lastNode) pusTreeCheckChange.call(this, lastNode, false); + }); + } + + if (lastNode) { + pusTreeCheckChange.call(this, lastNode, false); + } this.pusTree.on('checkchange', pusTreeCheckChange, this); suppressPUsCheckTrackCall = false; - - } //handler + } // handler }, { - id: 'refresh', qtip: 'Refresh', scope: this, hidden: true, handler: reloadPUs - } - ], //tools + ], // tools margins: '0 0 0 0', border: false, listeners: { - 'checkchange': { + checkchange: { fn: pusTreeCheckChange, scope: this } - } //listeners - - }); //this.pusTree + } // listeners - // --------------------------------------------------------- + }); // this.pusTree this.pusTree.on('checkchange', pusTreeCheckChange, this); @@ -531,10 +484,9 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { listeners: { - 'load': { + load: { fn: function (tstore) { - if (tstore.getCount() <= 0) { return; } @@ -547,46 +499,37 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { id: 'app_kernels', children: Ext.util.JSON.decode(tstore.getAt(0).get('nodes')) - }); //root + }); // root this.metricsTree.setRootNode(root); this.metricsTree.render(); root.expand(); - - }, //fn + }, // fn scope: this - - } - } //listeners - - }); //metricsStore - - // --------------------------------------------------------- + } // load + } // listeners + }); // metricsStore var reloadMetrics = function () { - Ext.apply(metricsStore.baseParams, getBaseParams.call(this)); metricsStore.load(); - - }; //reloadMetrics - - // --------------------------------------------------------- + }; // reloadMetrics var suppressMetricCheckTrackCall = false; var metricsTreeCheckChange = function (node, checked) { - - if (suppressMetricCheckTrackCall == false) - XDMoD.TrackEvent('App Kernel Explorer', 'Clicked a checkbox in the Metrics tree', Ext.encode({item: node.getPath('text'), checked: checked})); + if (suppressMetricCheckTrackCall === false) { + XDMoD.TrackEvent('App Kernel Explorer', 'Clicked a checkbox in the Metrics tree', Ext.encode({ + item: node.getPath('text'), + checked: checked + })); + } reloadResources.call(this); - //reloadPUs.call(this); + // eslint-disable-next-line no-use-before-define reloadChart.call(this); - - }; //metricsTreeCheckChange - - // --------------------------------------------------------- + }; // metricsTreeCheckChange this.metricsTree = new Ext.tree.TreePanel({ @@ -607,7 +550,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { draggable: false, id: 'app_kernels' - }, //root + }, // root rootVisible: false, containerScroll: true, @@ -621,7 +564,6 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { scope: this, handler: function () { - XDMoD.TrackEvent('App Kernel Explorer', 'Clicked on the uncheck all tool in the Metrics tree'); suppressMetricCheckTrackCall = true; @@ -630,54 +572,55 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { var selectAll = true; - this.metricsTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if(ui.isChecked()) selectAll=false; - lastNode = n; + this.metricsTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + selectAll = false; + } + lastNode = n; }); - if(selectAll){ - XDMoD.TrackEvent('App Kernel Explorer', 'Checking all (rendered) items in Metrics tree'); - this.metricsTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if(!ui.isChecked()) ui.toggleCheck(true); - lastNode = n; - }); + if (selectAll) { + XDMoD.TrackEvent('App Kernel Explorer', 'Checking all (rendered) items in Metrics tree'); + this.metricsTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (!ui.isChecked()) { + ui.toggleCheck(true); + } + lastNode = n; + }); + } else { + XDMoD.TrackEvent('App Kernel Explorer', 'Clearing all (rendered) checked items in Metrics tree'); + this.metricsTree.getRootNode().cascade(function (n) { + var ui = n.getUI(); + if (ui.isChecked()) { + ui.toggleCheck(false); + } + lastNode = n; + }); } - else{ - XDMoD.TrackEvent('App Kernel Explorer', 'Clearing all (rendered) checked items in Metrics tree'); - this.metricsTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if(ui.isChecked()) ui.toggleCheck(false); - lastNode = n; - }); + if (lastNode) { + metricsTreeCheckChange.call(this, lastNode, false); } - if (lastNode) metricsTreeCheckChange.call(this, lastNode, false); this.metricsTree.on('checkchange', metricsTreeCheckChange, this); suppressMetricCheckTrackCall = false; - - } //handler - + } // handler }, - { - id: 'refresh', qtip: 'Refresh', scope: this, hidden: true, handler: reloadMetrics - } - - ], //tools + ], // tools margins: '0 0 0 0', border: false, listeners: { - 'checkchange': { + checkchange: { fn: metricsTreeCheckChange, scope: this }, @@ -688,31 +631,27 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { var end_time = self.getDurationSelector().getEndDate() / 1000.0; var enabled = (start_time <= n.attributes.end_ts && n.attributes.end_ts <= end_time) || (n.attributes.start_ts <= end_time && end_time <= n.attributes.end_ts); - if (enabled) n.enable(); - else n.disable(); + if (enabled) { + n.enable(); + } else { + n.disable(); + } }, scope: this }, - expandnode: function(n) { - - XDMoD.TrackEvent('App Kernel Explorer', 'Expanded item in Metrics tree', n.getPath('text')); - - },//expandnode + expandnode: function (n) { + XDMoD.TrackEvent('App Kernel Explorer', 'Expanded item in Metrics tree', n.getPath('text')); + }, // expandnode - collapsenode: function(n) { - - XDMoD.TrackEvent('App Kernel Explorer', 'Collapsed item in Metrics tree', n.getPath('text')); - - }//collapsenode - - }, //listeners + collapsenode: function (n) { + XDMoD.TrackEvent('App Kernel Explorer', 'Collapsed item in Metrics tree', n.getPath('text')); + } // collapsenode + }, // listeners flex: 5 - }); //this.metricsTree - - // --------------------------------------------------------- + }); // this.metricsTree this.metricsTree.on('checkchange', metricsTreeCheckChange, this); @@ -729,32 +668,21 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { scope: this, change: function (t, n, o) { - - if (n != o) { - + if (n !== o) { XDMoD.TrackEvent('App Kernel Explorer', 'Updated chart title', t.getValue()); + // eslint-disable-next-line no-use-before-define reloadChart.call(this); - } - - }, //change + }, // change specialkey: function (t, e) { - - if (t.isValid(false) && e.getKey() == e.ENTER) { - - //XDMoD.TrackEvent('App Kernel Explorer', 'Updated chart title', t.getValue()); + if (t.isValid(false) && e.getKey() === e.ENTER) { + // eslint-disable-next-line no-use-before-define reloadChart.call(this); - } - - } //specialkey - - } //listeners - - }); //this.chartTitleField - - // --------------------------------------------------------- + } // specialkey + } // listeners + }); // this.chartTitleField this.legendTypeComboBox = new Ext.form.ComboBox({ @@ -795,7 +723,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { ] - }), //store + }), // store disabled: false, value: this.legend_type, @@ -807,20 +735,15 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { scope: this, - 'select': function (combo, record, index) { - - XDMoD.TrackEvent('App Kernel Explorer', 'Updated legend placement', Ext.encode({legend_type: record.get('id')})); + select: function (combo, record, index) { + XDMoD.TrackEvent('App Kernel Explorer', 'Updated legend placement', Ext.encode({ legend_type: record.get('id') })); this.legend_type = record.get('id'); + // eslint-disable-next-line no-use-before-define reloadChart.call(this); - - } //select - - } //listeners - - }); //this.legendTypeComboBox - - // --------------------------------------------------------- + } // select + } // listeners + }); // this.legendTypeComboBox this.fontSizeSlider = new Ext.slider.SingleSlider({ @@ -837,20 +760,15 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { scope: this, - 'change': function (t, n, o) { - - XDMoD.TrackEvent('App Kernel Explorer', 'Used the font size slider', Ext.encode({font_size: t.getValue()})); + change: function (t, n, o) { + XDMoD.TrackEvent('App Kernel Explorer', 'Used the font size slider', Ext.encode({ font_size: t.getValue() })); this.font_size = t.getValue(); + // eslint-disable-next-line no-use-before-define reloadChart.call(this); - - } //change - - } //listeners - - }); //this.fontSizeSlider - - // --------------------------------------------------------- + } // change + } // listeners + }); // this.fontSizeSlider this.northPanel = new Ext.Panel({ @@ -865,9 +783,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { items: [this.resourcesTree, this.pusTree], flex: 4 - }); //this.northPanel - - // --------------------------------------------------------- + }); // this.northPanel var leftPanel = new Ext.Panel({ @@ -915,7 +831,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { ] - }] //items + }] // items }, @@ -938,11 +854,9 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { } - ] //items - - }); //leftPanel + ] // items - // --------------------------------------------------------- + }); // leftPanel var chartStore = new CCR.xdmod.CustomJsonStore({ @@ -980,21 +894,22 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { method: 'POST', url: 'controllers/data_explorer.php' - }) //proxy - - }); //chartStore + }) // proxy - // --------------------------------------------------------- + }); // chartStore chartStore.on('beforeload', function () { - - if (!self.getDurationSelector().validate()) return; + if (!self.getDurationSelector().validate()) { + return; + } + // eslint-disable-next-line no-use-before-define highChartPanel.un('resize', onResize, this); + // eslint-disable-next-line no-use-before-define maximizeScale.call(this); Ext.apply(chartStore.baseParams, getBaseParams.call(this)); - chartStore.baseParams.timeframe_label = self.getDurationSelector().getDurationLabel(), + chartStore.baseParams.timeframe_label = self.getDurationSelector().getDurationLabel(); chartStore.baseParams.show_guide_lines = 'y'; chartStore.baseParams.scale = 1; chartStore.baseParams.format = 'hc_jsonstore'; @@ -1003,20 +918,34 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { chartStore.baseParams.controller_module = self.getReportCheckbox().getModule(); - }, this); //chartStore.on('beforeload' - - // --------------------------------------------------------- + // While we're still pre-load make sure to mask the appropriate + // component so that the User knows that we're retrieving data. + // eslint-disable-next-line no-use-before-define + view.el.mask('Loading...'); + }, this); // chartStore.on('beforeload' + // eslint-disable-next-line no-shadow chartStore.on('load', function (chartStore) { + // Now that we're done loading, make sure to unmask the appropriate + // component so that the User knows that we're done. + // eslint-disable-next-line no-use-before-define + view.el.unmask(); + + // Ensure that we unmask the main interface once we're done loading + // too. + var viewer = CCR.xdmod.ui.Viewer.getViewer(); + if (viewer.el) { + viewer.el.unmask(); + } - if (chartStore.getCount() != 1) { + if (chartStore.getCount() !== 1) { return; } var selectedResourceIds = this.getSelectedResourceIds(); var selectedMetrics = this.getSelectedMetrics(); var noData = selectedResourceIds.length === 0 || selectedMetrics.length === 0; - + // eslint-disable-next-line no-use-before-define chartViewPanel.getLayout().setActiveItem(noData ? 1 : 0); self.getExportMenu().setDisabled(noData); @@ -1031,12 +960,9 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { reportGeneratorMeta.start_date, reportGeneratorMeta.end_date, reportGeneratorMeta.included_in_report); - + // eslint-disable-next-line no-use-before-define highChartPanel.on('resize', onResize, this); - - }, this); //chartStore.on('load' - - // --------------------------------------------------------- + }, this); // chartStore.on('load' var reloadChartFunc = function () { chartStore.load(); @@ -1048,17 +974,6 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { reloadChartTask.delay(delay || 2000); }; - var chartViewTemplate = new Ext.XTemplate( - '', - '
', - '{chart_map}', - '', - '
', - '
' - ); - - // --------------------------------------------------------- - var assistPanel = new CCR.xdmod.ui.AssistPanel({ region: 'center', @@ -1068,18 +983,14 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { graphic: 'gui/images/ak_explorer_instructions.png', userManualRef: 'app+kernel+explorer' - }); //assistPanel - - // --------------------------------------------------------- + }); // assistPanel var highChartPanel = new CCR.xdmod.ui.HighChartPanel({ id: 'hc-panel' + this.id, store: chartStore - }); //highChartPanel - - // --------------------------------------------------------- + }); // highChartPanel var chartViewPanel = new Ext.Panel({ @@ -1096,49 +1007,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { assistPanel ] - }); //chartViewPanel - - // --------------------------------------------------------- - - //self.getDurationSelector().dateSlider.region = 'south'; - - var datasheetTab = new Ext.Panel({ - title: 'Datasheet' - }); - - var detailsTab = new Ext.Panel({ - title: 'Details' - }); - - var glossaryTab = new Ext.Panel({ - title: 'Glossary' - }); - - var southTabPanel = new Ext.TabPanel({ - - activeTab: 0, - region: 'center', - items: [datasheetTab, detailsTab, glossaryTab] - - }); //southTabPanel - - // --------------------------------------------------------- - - var southView = new Ext.Panel({ - - hideTitle: true, - split: true, - collapsible: true, - header: false, - collapseMode: 'mini', - region: 'south', - height: 250, - layout: 'border', - items: [southTabPanel] - - }); //southView - - // --------------------------------------------------------- + }); // chartViewPanel var view = new Ext.Panel({ @@ -1148,12 +1017,9 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { border: true, items: [chartViewPanel] - }); //view - - // --------------------------------------------------------- + }); // view self.on('print_clicked', function () { - var parameters = chartStore.baseParams; parameters.scale = 1; @@ -1163,8 +1029,10 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { var params = ''; - for (i in parameters) { - params += i + '=' + parameters[i] + '&'; + for (var i in parameters) { + if (parameters.hasOwnProperty(i)) { + params += i + '=' + parameters[i] + '&'; + } } params = params.substring(0, params.length - 1); @@ -1177,22 +1045,15 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { html: '' }); - - }); //self.on('print_clicked',... - - // --------------------------------------------------------- + }); // self.on('print_clicked',... self.on('export_option_selected', function (opts) { - var parameters = chartStore.baseParams; Ext.apply(parameters, opts); - CCR.invokePost("controllers/data_explorer.php", parameters); - - }); //self.on('export_option_selected', … - - // --------------------------------------------------------- + CCR.invokePost('controllers/data_explorer.php', parameters); + }); // self.on('export_option_selected', … var toggleChangeIndicator = new Ext.Button({ @@ -1208,43 +1069,27 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { pressed: true, tooltip: 'When this option is checked, each app kernel data series plot will show an exclamation point icon whenever a change has occurred in the execution environment of the app kernel (library version, compiler version, etc).' - }); //toggleChangeIndicator - - // --------------------------------------------------------- + }); // toggleChangeIndicator function reloadAll() { - reloadResources.call(this); reloadPUs.call(this); reloadMetrics.call(this); reloadChart.call(this, 150); - - } //reloadAll - - // --------------------------------------------------------- + } // reloadAll this.on('render', reloadAll, this, { single: true }); - // --------------------------------------------------------- - function maximizeScale() { - chartWidth = chartViewPanel.getWidth(); chartHeight = chartViewPanel.getHeight() - (chartViewPanel.tbar ? chartViewPanel.tbar.getHeight() : 0); - - } //maximizeScale - - // --------------------------------------------------------- + } // maximizeScale function onResize(t) { - maximizeScale.call(this); - - } //onResize - - // --------------------------------------------------------- + } // onResize Ext.apply(this, { @@ -1260,10 +1105,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelExplorer, XDMoD.PortalModule, { items: [leftPanel, view] - }); //Ext.apply + }); // Ext.apply XDMoD.Module.AppKernels.AppKernelExplorer.superclass.initComponent.apply(this, arguments); - - } //initComponent - -}); //XDMoD.Module.AppKernels.AppKernelExplorer + } // initComponent +}); // XDMoD.Module.AppKernels.AppKernelExplorer diff --git a/html/gui/js/modules/app_kernels/AppKernelViewer.js b/html/gui/js/modules/app_kernels/AppKernelViewer.js index d7b4fb9..7f89f0a 100644 --- a/html/gui/js/modules/app_kernels/AppKernelViewer.js +++ b/html/gui/js/modules/app_kernels/AppKernelViewer.js @@ -1,3 +1,4 @@ +/* global XDMoD, CCR, Ext, document, jQuery */ /** * This class contains functionality for the App Kernels tab. * @@ -31,7 +32,7 @@ Ext.apply(XDMoD.Module.AppKernels.AppKernelViewer, { * @param {Number} kernel_id The selected kernel_id. */ selectChildAppKernelChart: function (metric_id, resource_id, kernel_id) { - if (metric_id == -1 || resource_id == -1 || kernel_id == -1) { + if (metric_id === -1 || resource_id === -1 || kernel_id === -1) { return; } @@ -71,8 +72,8 @@ Ext.apply(XDMoD.Module.AppKernels.AppKernelViewer, { } if ( - node.attributes.type == 'appkernel' && - node.attributes.ak_id == kernel_id + node.attributes.type === 'appkernel' && + node.attributes.ak_id === kernel_id ) { var nodeToExpand = node.findChild('resource_id', resource_id); @@ -98,8 +99,8 @@ Ext.apply(XDMoD.Module.AppKernels.AppKernelViewer, { tree.getSelectionModel().select(nodeToSelect); }); } else if ( - node.attributes.type == 'resource' && - node.attributes.resource_id == resource_id + node.attributes.type === 'resource' && + node.attributes.resource_id === resource_id ) { var nodeToSelect = node.findChild('metric_id', metric_id, true); @@ -112,10 +113,8 @@ Ext.apply(XDMoD.Module.AppKernels.AppKernelViewer, { } tree.getSelectionModel().select(nodeToSelect); - } else { - if (viewer.el) { - viewer.el.unmask(); - } + } else if (viewer.el) { + viewer.el.unmask(); } }); }, @@ -202,7 +201,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { font_size: 3, swap_xy: false, showDateChooser: true, - current_hash:'', + current_hash: '', chartDataFields: [ 'hc_jsonstore', 'title', @@ -298,13 +297,12 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { chartThumbScale: CCR.xdmod.ui.thumbChartScale, chartWidth: 740, chartHeight: 345, - leftPanelWidth:375, + leftPanelWidth: 375, /** * Initialize app kernel module. */ initComponent: function () { - var treeTb = new Ext.Toolbar({ items: [ '->', @@ -348,6 +346,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { requestMethod: 'GET', listeners: { beforeload: function (loader, node, callback) { + // eslint-disable-next-line no-param-reassign loader.baseParams = XDMoD.REST.removeEmptyParameters({ ak: node.attributes.ak_id, resource: node.attributes.resource_id, @@ -469,13 +468,13 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { // Is a single chart being displayed? var isChart = - n.attributes.type == 'units' || - n.attributes.type == 'metric'; + n.attributes.type === 'units' || + n.attributes.type === 'metric'; // Are multiple charts being displayed with the menu? var isMenu = - n.attributes.type == 'resource' || - n.attributes.type == 'appkernel'; + n.attributes.type === 'resource' || + n.attributes.type === 'appkernel'; // Delete the current chart if only one will be displayed. if (isChart && this.chart) { @@ -498,7 +497,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { this.updateDescriptionLarge( chartStore, - n.attributes.type != 'appkernel' + n.attributes.type !== 'appkernel' ); XDMoD.TrackEvent( @@ -537,18 +536,17 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { var id = r.get('random_id'); var task = new Ext.util.DelayedTask(function () { - // Calculate width and height depending on if // multiple chart are displayed or just one. var width = isMenu ? - CCR.xdmod.ui.thumbWidth * this.chartThumbScale : - this.chartWidth * this.chartScale; + CCR.xdmod.ui.thumbWidth * this.chartThumbScale : + this.chartWidth * this.chartScale; var height = isMenu ? - CCR.xdmod.ui.thumbHeight * this.chartThumbScale : - this.chartHeight * this.chartScale; + CCR.xdmod.ui.thumbHeight * this.chartThumbScale : + this.chartHeight * this.chartScale; var baseChartOptions = { chart: { @@ -558,7 +556,6 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { animation: false, events: { load: function (e) { - // Check if an empty data set was // returned. If not, display the // "no data" image. @@ -587,8 +584,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { } } }, - plotOptions:{ - series:{ + plotOptions: { + series: { animation: false, point: { events: { @@ -686,7 +683,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { }); this.view = new Ext.DataView({ - loadingText: "Loading...", + loadingText: 'Loading...', itemSelector: 'chart_thumb-wrap', style: 'overflow:auto', multiSelect: true, @@ -717,7 +714,13 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { // Update the selected tree node when the panel is activated. this.on('activate', function (panel) { + var token = CCR.tokenize(document.location.hash); + // If we've received the activate event but the token does not specify + // us as the subtab then exit. + if (token.subtab && token.subtab !== this.id) { + return; + } // If the tree is already loading, replace the "load" // handler. Otherwise, the node can be selected // immediately. @@ -847,8 +850,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { scope: this, iconCls: 'exclamation', toggleHandler: function (b) { - XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({pressed: b.pressed})); - this.reloadChartStore(); + XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({ pressed: b.pressed })); + this.reloadChartStore(); }, pressed: false, tooltip: 'For each app kernel plot, show an exclamation point icon whenever a change has occurred in the execution environment (library version, compiler version, etc).' @@ -861,8 +864,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { scope: this, iconCls: '', toggleHandler: function (b) { - XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({pressed: b.pressed})); - this.reloadChartStore(); + XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({ pressed: b.pressed })); + this.reloadChartStore(); }, pressed: true, tooltip: 'Show the running average values as a dashed line on the chart. The running average is the linear average of the last five values.' @@ -875,8 +878,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { scope: this, iconCls: '', toggleHandler: function (b) { - XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({pressed: b.pressed})); - this.reloadChartStore(); + XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({ pressed: b.pressed })); + this.reloadChartStore(); }, pressed: true, tooltip: 'Show a band on the chart representing the values of the running average considered "In Control" at any given time.
A control region is picked to be first few points in a dataset and updated whenever an execution environment change is detected by the app kernel system. The control band then is calculated by clustering the control region into two sets based on the median and then finding the average of each set. The two averages define the control band.' @@ -889,8 +892,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { scope: this, iconCls: '', toggleHandler: function (b) { - XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({pressed: b.pressed})); - this.reloadChartStore(); + XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({ pressed: b.pressed })); + this.reloadChartStore(); }, pressed: true, tooltip: 'Show a red interval on the plot when the control value falls below -0.5, indicating an out of control (worse than expected) running average, and a green interval when the control value is greater than 0, indicating a better than control (better than expected) running average. Other running average values are considered "In Control"' @@ -903,8 +906,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { scope: this, iconCls: '', toggleHandler: function (b) { - XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({pressed: b.pressed})); - this.reloadChartStore(); + XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({ pressed: b.pressed })); + this.reloadChartStore(); }, pressed: false, listeners: { @@ -919,7 +922,6 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { this.toggleControlZones.show(); this.toggleRunningAverages.show(); this.toggleControlInterval.show(); - }, hide: function () { this.toggleDiscreteControls.hide(); @@ -944,8 +946,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { scope: this, iconCls: '', toggleHandler: function (b) { - XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({pressed: b.pressed})); - this.reloadChartStore(); + XDMoD.TrackEvent('App Kernels', 'Clicked on ' + b.getText(), Ext.encode({ pressed: b.pressed })); + this.reloadChartStore(); }, hidden: true, pressed: false, @@ -961,13 +963,13 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { listeners: { scope: this, change: function (t, n, o) { - if (n != o) { + if (n !== o) { XDMoD.TrackEvent('App Kernels', 'Updated title', t.getValue()); this.reloadChartStore(); } }, specialkey: function (t, e) { - if (t.isValid(false) && e.getKey() == e.ENTER) { + if (t.isValid(false) && e.getKey() === e.ENTER) { XDMoD.TrackEvent('App Kernels', 'Updated title', t.getValue()); this.reloadChartStore(); } @@ -1015,7 +1017,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { listeners: { scope: this, select: function (combo, record, index) { - XDMoD.TrackEvent('App Kernels', 'Updated legend placement', Ext.encode({legend_type: record.get('id')})); + XDMoD.TrackEvent('App Kernels', 'Updated legend placement', Ext.encode({ legend_type: record.get('id') })); this.legend_type = record.get('id'); this.reloadChartStore(2000); @@ -1034,7 +1036,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { listeners: { scope: this, change: function (t, n, o) { - XDMoD.TrackEvent('App Kernels', 'Used the font size slider', Ext.encode({font_size: t.getValue()})); + XDMoD.TrackEvent('App Kernels', 'Used the font size slider', Ext.encode({ font_size: t.getValue() })); this.font_size = t.getValue(); this.reloadChartStore(2000); @@ -1123,15 +1125,16 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { var thumbWidth = CCR.xdmod.ui.thumbWidth * this.chartThumbScale; - var portalWidth = viewer.getWidth()-this.leftPanelWidth; + var portalWidth = viewer.getWidth() - this.leftPanelWidth; - portalWidth = portalWidth - (CCR.xdmod.ui.scrollBarWidth - CCR.xdmod.ui.thumbPadding / 2); + // eslint-disable-next-line no-mixed-operators + portalWidth -= (CCR.xdmod.ui.scrollBarWidth - CCR.xdmod.ui.thumbPadding / 2); - if(portalWidth<50.0){ - portalWidth=this.chartWidth; + if (portalWidth < 50.0) { + portalWidth = this.chartWidth; } - var portalColumnsCount = Math.max(1, Math.round(portalWidth / thumbWidth) ); + var portalColumnsCount = Math.max(1, Math.round(portalWidth / thumbWidth)); thumbWidth = portalWidth / portalColumnsCount; thumbWidth -= CCR.xdmod.ui.thumbPadding; @@ -1204,7 +1207,6 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { if (node.childNodes.length > 0) { tree.expandPath(path, null, expander); } else { - // Delay the expansion so the child nodes are ready. new Ext.util.DelayedTask(function () { tree.expandPath(path, null, expander); @@ -1271,6 +1273,14 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { * Select the first node in the tree if none are selected. */ selectFirstNode: function () { + var token = CCR.tokenize(document.location.hash); + + // If we've received the activate event but the token does not specify + // us as the subtab then exit. + if (token.subtab && token.subtab !== this.id) { + return; + } + var sm = this.tree.getSelectionModel(); var node = sm.getSelectedNode(); @@ -1282,15 +1292,15 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { var root = this.tree.getRootNode(); if (root.hasChildNodes()) { - var selected=false; + var selected = false; for (var i = 0; i < root.childNodes.length; i++) { - if(root.childNodes[i].disabled===false){ + if (root.childNodes[i].disabled === false) { sm.select(root.childNodes[i]); - selected=true; + selected = true; break; } } - if(selected===false){ + if (selected === false) { sm.select(root.childNodes[0]); } } else { @@ -1325,7 +1335,6 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { * @param {Ext.tree.TreeNode} n The selected tree node. */ getParameters: function (n) { - var parameters = { show_change_indicator: this.toggleChangeIndicator.pressed ? 'y' : 'n', collected: n.attributes.collected, @@ -1337,7 +1346,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { swap_xy: this.swap_xy }; - if (n.attributes.type == 'units') { + if (n.attributes.type === 'units') { parameters.num_proc_units = n.attributes.num_proc_units; parameters.metric = n.attributes.metric_id; parameters.resource = n.attributes.resource_id; @@ -1352,7 +1361,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { parameters.show_control_zones = this.toggleControlZones.pressed ? 'y' : 'n'; parameters.show_running_averages = this.toggleRunningAverages.pressed ? 'y' : 'n'; parameters.show_control_interval = this.toggleControlInterval.pressed ? 'y' : 'n'; - } else if (n.attributes.type == 'metric') { + } else if (n.attributes.type === 'metric') { parameters.metric = n.attributes.metric_id; parameters.resource = n.attributes.resource_id; parameters.ak = n.attributes.ak_id; @@ -1361,7 +1370,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { parameters.show_title = 'y'; parameters.width = this.chartWidth * this.chartScale; parameters.height = this.chartHeight * this.chartScale; - } else if (n.attributes.type == 'resource') { + } else if (n.attributes.type === 'resource') { parameters.resource = n.attributes.resource_id; parameters.ak = n.attributes.ak_id; parameters.width = CCR.xdmod.ui.thumbWidth * this.chartThumbScale; @@ -1370,8 +1379,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { parameters.format = 'session_variable'; parameters.thumbnail = 'y'; parameters.show_guide_lines = 'n'; - parameters.font_size = parameters.font_size - 3; - } else if (n.attributes.type == 'appkernel') { + parameters.font_size -= 3; + } else if (n.attributes.type === 'appkernel') { parameters.ak = n.attributes.ak_id; parameters.metric = 'Wall Clock Time'; parameters.width = CCR.xdmod.ui.thumbWidth * this.chartThumbScale; @@ -1380,7 +1389,7 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { parameters.format = 'session_variable'; parameters.thumbnail = 'y'; parameters.show_guide_lines = 'n'; - parameters.font_size = parameters.font_size - 3; + parameters.font_size -= 3; } return parameters; @@ -1399,7 +1408,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { // This will allow the user to selected a time period that // contains to data for the ak and then select a time period // where there is data for the ak. - if (this.selectedNode&&(!n || n.disabled)) { + if (this.selectedNode && (!n || n.disabled)) { + // eslint-disable-next-line no-param-reassign n = this.selectedNode; } else { this.selectedNode = n; @@ -1421,24 +1431,24 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { '?kernel=' + n.id + '&start=' + start + '&end=' + end; - this.current_hash=token + this.current_hash = token; Ext.History.add(token, true); this.images.setTitle(n.getPath('text')); - if (n.attributes.type == 'units') { + if (n.attributes.type === 'units') { this.toggleControlPlot.show(); } else { this.toggleControlPlot.hide(); } var isChart = - n.attributes.type == 'units' || - n.attributes.type == 'metric'; + n.attributes.type === 'units' || + n.attributes.type === 'metric'; var isMenu = - n.attributes.type == 'resource' || - n.attributes.type == 'appkernel'; + n.attributes.type === 'resource' || + n.attributes.type === 'appkernel'; XDMoD.TrackEvent( 'App Kernels', @@ -1468,10 +1478,8 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { } this.chartStore.load({ params: parameters }); - } else { - if (viewer.el) { - viewer.el.unmask(); - } + } else if (viewer.el) { + viewer.el.unmask(); } this.updateEnabledNodes(); }, @@ -1490,11 +1498,11 @@ Ext.extend(XDMoD.Module.AppKernels.AppKernelViewer, XDMoD.PortalModule, { var start = viewer.getParameterByName('start', token.content); var end = viewer.getParameterByName('end', token.content); - if (start !== "" && end !== "") { + if (start !== '' && end !== '') { this.getDurationSelector().setValues(start, end); } - if (token.subtab === panel.id) { + if (token.subtab === panel.id && kernel_id) { this.selectAppKernel(kernel_id); } else { this.selectFirstNode(); diff --git a/html/gui/js/modules/summary/CenterReportCardPortlet.js b/html/gui/js/modules/summary/CenterReportCardPortlet.js new file mode 100644 index 0000000..8825590 --- /dev/null +++ b/html/gui/js/modules/summary/CenterReportCardPortlet.js @@ -0,0 +1,388 @@ +/* global Ext, XDMoD, CCR, window */ +Ext.namespace('XDMoD.Modules.SummaryPortlets'); + +/** + * XDMoD.Modules.SummaryPortlets.CenterReportCardPortlet + * + * This portlet is responsible for displaying data that could be used to determine + * the "health" of a center. Specifically, this implementation shows App Kernel + * data for a given XDMoD Installation. Specifically it will display a horizontal + * stacked barchart of the failed, in control, and under / over performing runs + * of an app kernel by resource. + * + * The resources shown are restricted to those that the currently logged in user + * is authorized to view. This is done via their associated organization, + * `moddb.Users.organization_id`, which is in turn used as a filter via + * `modw.resourcefact.organization_id`. + * + * + * This portlet is driven by the REST Endpoint at + * `/app_kernels/performance_map/raw`. Which in turn is served by + * the `AppKernelControllerProvider::getRawPerformanceMap` function. + * + * It will display a message if no app kernel data is retrieved: + * - `this.grid.viewConfig.emptyText` + * + * It will mask itself while retrieving data: + * - `this.listeners.afterrender` + * + * It will unmask itself when it is done retrieving data or when an exception + * is detected: + * - `this.gridstore.listeners.load` + * - `this.gridstore.listeners.exception` + */ +XDMoD.Modules.SummaryPortlets.CenterReportCardPortlet = Ext.extend(Ext.ux.Portlet, { + + layout: 'fit', + autoScroll: true, + titleBase: 'Center Report Card', + + tools: [ + { + id: 'help', + qtip: [ + '
    ', + '
  • ', + ' ', + 'Failed Runs', + '
      ', + '
    • A run in which the app kernel failed to complete successfully.
    • ', + '
    ', + '
  • ', + '
  • ', + ' ', + 'Under Performing Runs', + '
      ', + '
    • A run in which the app kernel completed successfully but performed below the established control region.
    • ', + '
    ', + '
  • ', + '
  • ', + ' ', + 'In Control Runs', + '
      ', + '
    • A run in which the app kernel completed successfully and performed within the established control region.
    • ', + '
    ', + '
  • ', + '
  • ', + ' ', + 'Over Performing Runs', + '
      ', + '
    • A run in which the app kernel completed successfully and performed better than the established control region.
    • ', + '
    ', + '
  • ', + '
' + ].join(' '), + qwidth: 60 + } + ], + + /** + * Constructor for the CenterReportCardPortlet. + */ + initComponent: function () { + var self = this; + + var aspectRatio = 0.8; + this.height = this.width * aspectRatio; + + 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); + } + } + /** + * The main datastore for this portlet. It retrieves app kernel run data + * for the specified time period from `this.config.start_date` to + * `this.config.end_date`. These values are populated w/ the value from + * the XDMoD Duration Toolbar. + */ + this.gridStore = new CCR.xdmod.CustomJsonStore({ + storeId: 'center-report-card-store', + root: 'results', + autoLoad: true, + fields: [ + 'resource', + 'app_kernel', + 'failedRuns', + 'inControlRuns', + 'overPerformingRuns', + 'underPerformingRuns' + ], + proxy: new Ext.data.HttpProxy({ + method: 'GET', + url: XDMoD.REST.url + '/app_kernels/performance_map/raw' + }), + baseParams: { + start_date: this.config.start_date, + end_date: this.config.end_date + }, + listeners: { + load: function () { + // Make sure that once we're loaded we remove this portlets + // mask. This was added during the `afterrender` event. + self.el.unmask(); + }, + exception: function () { + // refresh the grid view so that we can apply the empty text. + self.grid.getView().refresh(); + + // unmask so that the user can see what's going on. + self.el.unmask(); + } + } + }); // this.gridStore = new CCR.xdmod.CustomJsonStore({ + + /** + * A custom column renderer used to generate a stacked horizontal + * barchart of each app kernels failed, in control, over and under + * performing runs for a given time period. + * + * @param value {Object} *** UNUSED *** + * @param metaData {Object} *** UNUSED *** + * @param record {Ext.data.Record} record provided by the `gridStore` + * that contains the app kernel run data to be rendered. + * + * @returns {string} + */ + var valueRenderer = function (value, metaData, record) { + var failed = record.get('failedRuns'); + var inControl = record.get('inControlRuns'); + var overPerforming = record.get('overPerformingRuns'); + var underPerforming = record.get('underPerformingRuns'); + + var total = failed + inControl + overPerforming + underPerforming; + + /** + * Constructs an svg `rect` element based on the provided attributes. + * This will be used in a stacked horizontal bar chart. + * + * @param id {String} The id to use for the rect element. + * @param title {String} The title to display for this elements tooltip + * @param msg {String} The msg to display w/ this elements tooltip + * @param width {Number} The width of this element ( will be interpreted as a percentage ). + * @param x {Number} The distance from the left that this element should reside ( will be interpreted as a percentage ). + * @param height {Number} The height of this rect element. + * @param red {Number} The r of this elements rgb. + * @param green {Number} The g of this elements rgb. + * @param blue {Number} The b of this elements rgb. + * + * @returns {string} for an svg rect element + */ + var rect = function (id, title, msg, width, x, height, red, green, blue) { + var xValue = x + '%'; + if (Ext.isChrome) { + xValue = 'calc(' + x + '% + 1px)'; + } + return [ + '' + ].join(' '); + }; + + var height = 20; + + // Make sure that we have at least some runs + if (total > 0) { + var contents = [ + '
', + '' + ]; + + var input = { + 'ak-failed': { + title: 'Failed Runs', + red: 255, + green: 0, + blue: 0, + runs: failed + }, + 'ak-underperforming': { + title: 'Under Performing Runs', + red: 255, + green: 179, + blue: 54, + runs: underPerforming + }, + 'ak-incontrol': { + title: 'In Control Runs', + red: 80, + green: 180, + blue: 50, + runs: inControl + }, + 'ak-overperforming': { + title: 'Over Performing Runs', + red: 66, + green: 134, + blue: 255, + runs: overPerforming + } + }; + + var sum = 0; + for (var id in input) { + if (input.hasOwnProperty(id)) { + var runs = input[id].runs; + var percentage = total > 0 ? Math.round(((runs / total) * 100)) : 0; + var msg = percentage + '% - ( ' + (runs / total) + ' )'; + + contents.push( + rect(id, input[id].title, msg, percentage, sum, height, input[id].red, input[id].green, input[id].blue) + ); + + sum += percentage; + } + } + + contents.push(''); + contents.push('
'); + } else { + // If we don't have any runs then just output a simple message + // to let the user know what's up. + // eslint-disable-next-line block-scoped-var + contents = [ + '
', + 'No Data Found!', + '
' + ]; + } + + // eslint-disable-next-line block-scoped-var + return contents.join(' '); + }; // var valueRenderer = function (value, metaData, record) { + + /** + * The main visual element for this portlet. Displays App Kernels by + * resource, name, and a stacked horizontal bar chart of the failed, + * under performing, in control and over performing runs. + */ + this.grid = new Ext.grid.GridPanel({ + width: 200, + store: this.gridStore, + autoExpandColumn: 'ak-status', + colModel: new Ext.grid.ColumnModel({ + columns: [ + { + id: 'ak-resource', + header: 'Resource', + dataIndex: 'resource', + width: 90 + }, + { + id: 'ak-name', + header: 'App Kernel', + dataIndex: 'app_kernel', + width: 190 + }, + { + id: 'ak-status', + header: 'Status', + dataIndex: 'inControlRuns', + renderer: valueRenderer + } + ] + }), + viewConfig: new Ext.grid.GridView({ + emptyText: 'No App Kernel information available.' + }), + listeners: { + + /** + * Fires when the user clicks on a row. In this case we construct + * a new History token that will direct the UI to the App Kernel + * Performance Map tab w/ the currently selected start date and + * end date so that the tabs duration toolbar can be set correctly. + * The resource / app kernel is also included so that the correct + * first row can be selected. + * + * @param {Ext.grid.GridPanel} grid + * @param {number} rowIndex + */ + rowclick: function (grid, rowIndex) { + var record = grid.getStore().getAt(rowIndex); + + var info = { + start_date: self.config.start_date, + end_date: self.config.end_date, + resource: record.get('resource'), + app_kernel: record.get('app_kernel') + }; + + var token = 'main_tab_panel:app_kernels:app_kernel_performance_map?ak=' + window.btoa(JSON.stringify(info)); + + Ext.History.add(token); + } + } + }); // this.grid + + Ext.apply(this, { + items: [ + this.grid + ] + }); + + this.updateTitle(this.config.start_date, this.config.end_date); + + XDMoD.Modules.SummaryPortlets.CenterReportCardPortlet.superclass.initComponent.apply(this, arguments); + }, // initComponent + + listeners: { + duration_change: function (timeframe) { + // Mask the portlet as we're going to be loading new data. + this.el.mask('Loading...'); + + this.gridStore.load({ + params: { + start_date: timeframe.start_date, + end_date: timeframe.end_date + } + }); + + // Make sure that the portlet title reflects the updated start / end + // date + this.updateTitle(timeframe.start_date, timeframe.end_date); + + // save the new timeframe data for later use. + this.config.start_date = timeframe.start_date; + this.config.end_date = timeframe.end_date; + }, + + afterrender: function () { + this.el.mask('Loading...'); + } + }, // listeners: { + + /** + * A helper function that will update this portlet's title attribute w/ + * the start / end date used to generate the data it is displaying. + * + * @param {Date} startDate + * @param {Date} endDate + */ + updateTitle: function (startDate, endDate) { + this.setTitle( + this.titleBase + ' - ' + + startDate + + ' to ' + + endDate); + }, // updateTitle: function(startDate, endDate) { + + formatDate: function (date) { + return date.getFullYear() + '-' + + ('' + (date.getMonth() + 1)).padStart(2, '0') + '-' + + ('' + date.getDate()).padStart(2, '0'); + } // formatDate: function(date) { + +}); // XDMoD.Modules.SummaryPortlets.CenterReportCardPortlet = Ext.extend(Ext.ux.Portlet, { + +Ext.reg('CenterReportCardPortlet', XDMoD.Modules.SummaryPortlets.CenterReportCardPortlet); diff --git a/html/internal_dashboard/js/Arr/AppKerPerformanceMapPanel.js b/html/internal_dashboard/js/Arr/AppKerPerformanceMapPanel.js deleted file mode 100644 index 2e74250..0000000 --- a/html/internal_dashboard/js/Arr/AppKerPerformanceMapPanel.js +++ /dev/null @@ -1,800 +0,0 @@ -/** - * ARR active tasks grid. - * - * @author Nikolay A. Simakov - */ - - -Ext.namespace('XDMoD', 'XDMoD.Arr','CCR', 'CCR.xdmod', 'CCR.xdmod.ui','Ext.ux.grid'); -Ext.QuickTips.init(); // enable tooltips - -XDMoD.Arr.AppKerPerformanceMapStore = Ext.extend(Ext.data.JsonStore, { - restful:true, - - proxy: XDMoD.REST.createHttpProxy({ - url: 'app_kernels/performance_map', - method: 'GET' - }), - - listeners : { - exception : function(misc) { - console.log(misc); - } - }, - - constructor : function(config) { - config = config || {}; - - var nowEpoch = Date.now(); - - Ext.apply(config, { - baseParams : { - } - }); - - XDMoD.Arr.AppKerPerformanceMapStore.superclass.constructor.call(this, config); - } -}); - - - -XDMoD.Arr.AppKerPerformanceMapGrid = Ext.extend(Ext.grid.GridPanel, { - id:'ak_perfmap', - loadMask : true, - listeners : { - viewready : function() { - this.store.load(); - console.log('viewready'); - } - }, - colorStyles:{'W':'style="background-color:white;"', - 'F':'style="background-color:#FFB0C4;"', - 'U':'style="background-color:#F7FE2E;"', - 'O':'style="background-color:#FE9A2E;"', - 'C':'style="background-color:#81BEF7;"', - 'N':'style="background-color:#B0FFC5;"', - 'R':'style="background-color:#F781F3;"'}, - rendererForCell : function(value, metaData, record, rowIndex, colIndex, store) { - if (value != ' ' && value != '') { - var v = value.split('/'); - for (var i = 1; i < v.length; i++) - v[i] = parseInt(v[i], 10); - if(v[0] in this.colorStyles){ - return '
' + value + '
'; - } - else{ -// return '
' + value + '
'; - } - } - return value || 0; - }, - metaDataChanged : function(store) { - console.log('metaDataChanged'); - var newColumns = [{ - header : 'Resource', - dataIndex : 'resource', - //align: 'right', - width : 80 - }, { - header : 'App Kernel', - dataIndex : 'appKer', - //align: 'right', - width : 90 - - }, { - header : 'Nodes', - dataIndex : 'problemSize', - align : 'right', - width : 50 - }]; - var nLocked = newColumns.length; - for (var i = nLocked; i < this.store.fields.getCount(); i++) { - var value = this.store.fields.itemAt(i).name; - var m_date = value.split('/'); - if (value.indexOf('Failed') >= 0) - continue; - if (value.indexOf('InControl') >= 0) - continue; - if (value.indexOf('OutOfControl') >= 0) - continue; - if (value.indexOf('IDs') >= 0) - continue; - - var day = m_date[2]; - - newColumns.push({ - header : day, - dataIndex : value, - align : 'center', - renderer : { - fn : this.rendererForCell, - scope : this - }, - width : 40 - }); - } - var newColModel = new Ext.grid.ColumnModel({ - defaults : { - sortable : false - }, - columns : newColumns - }); - - //var i; - //for(i=3;i= 0)continue; - - var monthYear = monthNames[parseInt(m_date[1]) - 1] + ", " + m_date[0]; - - if (this.dateGroup[0][this.dateGroup[0].length - 1].header == monthYear) - this.dateGroup[0][this.dateGroup[0].length - 1].colspan++; - else - this.dateGroup[0].push({ - header : monthYear, - colspan : 1, - align : 'center' - }); - } - newColModel.rows = this.dateGroup; - - this.reconfigure(this.store, newColModel); - }, - - constructor : function(config) { - config = config || {}; - - Ext.applyIf(config, { - //title: 'AppKer Success Rate' - }); - - //this.store = new XDMoD.Arr.AppKerPerformanceMapStore(); - this.store = new Ext.data.JsonStore({ - //url : 'controllers/arr_controller.php', - baseParams : { - //operation : 'get_performance_map' - }, - proxy: XDMoD.REST.createHttpProxy({ - url: 'app_kernels/performance_map', - method: 'GET' - }) - }); - - this.store.on("metadatachanged", this.metaDataChanged, this); - this.store.on("datachanged", this.metaDataChanged, this); - this.store.on("reconfigure", this.metaDataChanged, this); - - this.dateGroup = [[{ - header : "", - colspan : 3, - align : 'center' - }]/*,[{header:"",colspan:3,align:'center'}]*/]; - - var dateHeader = new Ext.ux.grid.ColumnHeaderGroup({ - rows : this.dateGroup - }); - this.plugins = [dateHeader]; - - Ext.apply(config, { - id : 'appKerPerformanceMapGrid', - cls : 'appKerPerfMap_grid', - columnLines : true, - enableColumnMove : false, - enableColumnHide : false, - colModel : new Ext.ux.grid.LockingColumnModel({ - - defaults : { - sortable : false - }, - columns : [{ - header : 'Resource', - dataIndex : 'resource' - //align: 'right', - //width: 100 - }, { - header : 'App Kernel', - dataIndex : 'appKer', - //align: 'right', - width : 100 - }, { - header : 'Nodes', - dataIndex : 'problemSize', - align : 'right', - width : 60 - }] - }), - plugins : dateHeader, - selModel : new Ext.grid.CellSelectionModel({ - singleSelect : true - }) - }); - - XDMoD.Arr.AppKerPerformanceMapGrid.superclass.constructor.call(this, config); - } -}); - -XDMoD.Arr.AppKerPerformanceMapPanel = function(config) { - XDMoD.Arr.AppKerPerformanceMapPanel.superclass.constructor.call(this, config); -}; - -Ext.apply(XDMoD.Arr.AppKerPerformanceMapPanel, { - //static stuff - -}); - - - -Ext.extend(XDMoD.Arr.AppKerPerformanceMapPanel, Ext.Panel, { - title : 'Performance Map', - resourcesList : ["blacklight", "edge", "edge12core", "lonestar4", "kraken", "trestles", "gordon", "stampede"], - problemSizeList : [1, 2, 4, 8, 16], - appKerList : ["xdmod.app.astro.enzo", "xdmod.app.chem.gamess", "xdmod.app.chem.nwchem", "xdmod.app.md.namd", "xdmod.benchmark.hpcc", "xdmod.benchmark.io.ior", "xdmod.benchmark.io.mpi-tile-io", "xdmod.benchmark.mpi.imb", "xdmod.benchmark.graph.graph500", "xdmod.bundle"], - legend: - 'Each day summarized in table cell as pair of a symbol and a number.'+ - 'Symbol represent the status of last application kernel execution on that day and number'+ - ' shows total number of runs. Each cell is colored according to the statu'+ - 's of last application kernel run. Below is the codes description:
<'+ - 'br/>Description'+ - ''+ - '
Code
NApplication kernel was executed within control interval
UApplication kernel w'+ - 'as under-performing
OApplication kernel was over-performing
FApplication kernel failed to ru'+ - 'n
CThis ru'+ - 'n was used to calculate control region
RApplication kernel have run, but control i'+ - 'nformation is not available
'+ - 'There was no application kernel runs
'+ - 'Select cell for more details', - getSelectedResources : function() { - /*var resources = []; - var selNodes = this.resourcesTree.getChecked(); - Ext.each(selNodes, function(node) { - //if(!node.disabled) - resources.push(node.text); - }); - return resources;*/ - return null; - }, - getSelectedProblemSizes : function() { - /*var problemSize = []; - var selNodes = this.problemSizesTree.getChecked(); - Ext.each(selNodes, function(node) { - //if(!node.disabled) - problemSize.push(node.text); - }); - return problemSize;*/ - return null; - }, - getSelectedAppKers : function() { - /*var appKers = []; - var selNodes = this.appKerTree.getChecked(); - Ext.each(selNodes, function(node) { - //if(!node.disabled) - appKers.push(node.text); - }); - return appKers;*/ - return null; - }, - initComponent : function() { - var appKerPerformanceMapGrid = new XDMoD.Arr.AppKerPerformanceMapGrid({ - scope : this, - region : "center" - }); - - this.appKerPerformanceMapGrid = appKerPerformanceMapGrid; - - var resourceChildren = []; - for (var i = 0; i < this.resourcesList.length; i++) { - var resource = this.resourcesList[i]; - resourceChildren.push({ - text : resource, - nick : resource, - type : "resource", - checked : true, - iconCls : "resource", - leaf : true - }) - } - - this.resourcesTree = new Ext.tree.TreePanel({ - title : 'Resources', - id : 'tree_resources_' + this.id, - useArrows : true, - autoScroll : true, - animate : false, - enableDD : false, - region : 'north', - //height: 200, - root : new Ext.tree.AsyncTreeNode({ - nodeType : 'async', - text : 'Resources', - draggable : false, - id : 'resources', - expanded : true, - children : resourceChildren - }), - rootVisible : false, - containerScroll : true, - tools : [{ - id : 'unselect', - qtip : 'De-select all selected resources.', - scope : this, - handler : function() { - this.resourcesTree.un('checkchange', reloadAll, this); - var lastNode = null; - var selectAll = true; - - this.resourcesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (ui.isChecked()) - selectAll = false; - lastNode = n; - }); - - if (selectAll) { - this.resourcesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (!ui.isChecked()) - ui.toggleCheck(true); - lastNode = n; - }); - } - else { - this.resourcesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (ui.isChecked()) - ui.toggleCheck(false); - lastNode = n; - }); - } - if (lastNode) - reloadAll.call(this); - this.resourcesTree.on('checkchange', reloadAll, this); - } - }, { - id : 'refresh', - qtip : 'Refresh', - hidden : true, - scope : this, - handler : reloadAll - }], - margins : '0 0 0 0', - border : false, - split : true, - flex : 4 - }); - - var problemSizeChildren = []; - for (var i = 0; i < this.problemSizeList.length; i++) { - var nodesSize = this.problemSizeList[i]; - problemSizeChildren.push({ - text : String(nodesSize), - qtip : (nodesSize == 1) ? nodesSize + "node" : nodesSize + "nodes", - type : "node", - checked : true, - iconCls : "node", - leaf : true - }); - } - - this.problemSizesTree = new Ext.tree.TreePanel({ - flex : 0.5, - title : "Problem Size (Cores or Nodes)", - id : 'tree_nodes_' + this.id, - useArrows : true, - autoScroll : true, - animate : false, - enableDD : false, - // loader: nodesTreeLoader, - - root : new Ext.tree.AsyncTreeNode({ - nodeType : 'async', - text : 'Resources', - draggable : false, - id : 'resources', - expanded : true, - children : problemSizeChildren - }), - tools : [{ - id : 'unselect', - qtip : 'De-select all selected resources.', - scope : this, - handler : function() { - this.problemSizesTree.un('checkchange', reloadAll, this); - var lastNode = null; - var selectAll = true; - - this.problemSizesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (ui.isChecked()) - selectAll = false; - lastNode = n; - }); - - if (selectAll) { - this.problemSizesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (!ui.isChecked()) - ui.toggleCheck(true); - lastNode = n; - }); - } - else { - this.problemSizesTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (ui.isChecked()) - ui.toggleCheck(false); - lastNode = n; - }); - } - if (lastNode) - reloadAll.call(this); - this.problemSizeTree.on('checkchange', reloadAll, this); - } - }, { - id : 'refresh', - qtip : 'Refresh', - hidden : true, - scope : this, - handler : reloadAll - }], - rootVisible : false, - containerScroll : true, - margins : '0 0 0 0', - border : false, - flex : 2 - }); - - var appKerChildren = []; - for (var i = 0; i < this.appKerList.length; i++) { - var appker = this.appKerList[i]; - appKerChildren.push({ - text : appker, - nick : appker, - type : "app_kernel", - checked : true, - iconCls : "appkernel", - leaf : true - }); - } - - this.appKerTree = new Ext.tree.TreePanel({ - title : 'App Kernels', - id : 'tree_appker_' + this.id, - useArrows : true, - autoScroll : true, - animate : false, - enableDD : false, - region : 'north', - //height: 200, - root : new Ext.tree.AsyncTreeNode({ - nodeType : 'async', - text : 'App Kernels', - draggable : false, - id : 'appker', - expanded : true, - children : appKerChildren - }), - tools : [{ - id : 'unselect', - qtip : 'De-select all selected resources.', - scope : this, - handler : function() { - this.appKerTree.un('checkchange', reloadAll, this); - var lastNode = null; - var selectAll = true; - - this.appKerTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (ui.isChecked()) - selectAll = false; - lastNode = n; - }); - - if (selectAll) { - this.appKerTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (!ui.isChecked()) - ui.toggleCheck(true); - lastNode = n; - }); - } - else { - this.appKerTree.getRootNode().cascade(function(n) { - var ui = n.getUI(); - if (ui.isChecked()) - ui.toggleCheck(false); - lastNode = n; - }); - } - if (lastNode) - reloadAll.call(this); - this.appKerTree.on('checkchange', reloadAll, this); - } - }, { - id : 'refresh', - qtip : 'Refresh', - hidden : true, - scope : this, - handler : reloadAll - }], - rootVisible : false, - containerScroll : true, - margins : '0 0 0 0', - border : false, - split : true, - flex : 4 - }); - var leftPanel = new Ext.Panel({ - split : true, - collapsible : true, - title : 'App Kernel/Resource Query', - //collapseMode: 'mini', - //header: false, - width : 325, - layout : { - type : 'vbox', - align : 'stretch' - }, - region : 'west', - margins : '2 0 2 2', - border : true, - plugins : new Ext.ux.collapsedPanelTitlePlugin(), - items : [this.resourcesTree, this.problemSizesTree, this.appKerTree] - }); - var commentsTemplate = new Ext.XTemplate( - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '
', - 'Resource: {resource}
', - '
', - 'Application Kernel: {appKer}
', - '
', - ' Failed runs: {failedJobs}
', - '
', - ' Under-performing runs: {underPerformingJobs}
', - '
', - ' Over-performing runs: {overPerformingJobs}
', - '
', - ' In control runs: {inControlJobs}
', - '
', - ' Runs used for control calculations: {controlJobs}
', - '
', - ' Runs without control information: {noControlInfoJobs}
', - '

', - this.legend - ); - this.commentsPanel = new Ext.Panel({ - id : 'commentsPanel', - region : 'south', - autoScroll : true, - border : true, - collapsible : true, - split : true, - title : 'Description', - height : 130, - html : this.legend - }); - //commentsPanel - this.appKerPerformanceMapGrid.getSelectionModel().on('cellselect', function(sm, rowIdx, colIndex) { - /*populate detailed view pannele with arr job ids*/ - var i; - var j; - - var selCell = sm.getSelectedCell(); - - var detailPanel = Ext.getCmp('commentsPanel'); - var dataIndex = sm.grid.getColumnModel().getDataIndex(colIndex); - var record = sm.grid.getStore().getAt(rowIdx); - - var failedRunsStr = ''; - var outOfControlRunsStr = ''; - var inControlRunsStr = ''; - var dataIndexAll = [dataIndex]; - //if columns with resource or appkernel name is selected show all jobs for the queried period - if (colIndex <= 2) { - dataIndexAll = []; - for ( j = 3; j < record.fields.getCount(); j++) { - var key = record.fields.getKey(record.fields.itemAt(j)); - if (key.indexOf('Failed') >= 0) - continue; - if (key.indexOf('InControl') >= 0) - continue; - if (key.indexOf('OutOfControl') >= 0) - continue; - dataIndexAll.push(key); - } - } - //pack jobs - var iStatus; - var statuses=['F','U','N','O','C','R']; - var ref={'F':'failedJobs', - 'U':'underPerformingJobs', - 'N':'inControlJobs', - 'O':'overPerformingJobs', - 'C':'controlJobs', - 'R':'noControlInfoJobs'}; - var jobsIDs={'F':'', - 'U':'', - 'N':'', - 'O':'', - 'C':'', - 'R':''}; - - for ( j = dataIndexAll.length - 1; j >= 0; j--) { - var dataIndexRun = dataIndexAll[j]; - var s; - for(iStatus=0;iStatus' + runs[i] + ''; - } - } - } - } - var dataValue = record.get(dataIndex); - var values = { - appKer : record.get('appKer'), - resource : record.get('resource'), - rowIdx : rowIdx, - colIndex : colIndex, - dataIndex : dataIndex, - dataValue : dataValue - }; - for(iStatus=0;iStatus * @author Nikolay A. Simakov - * + * */ Ext.namespace('XDMoD', 'XDMoD.Arr'); XDMoD.Arr.AppKernelDashboardPanel = Ext.extend(Ext.ux.GroupTabPanel, { title: 'App Kernels', - id:'appkernels', + id: 'appkernels', tabWidth: 140, constructor: function (config) { + /* eslint-disable no-param-reassign */ config = config || {}; - config.activeGroup=0; + config.activeGroup = 0; config.items = [ { items: [new XDMoD.Arr.SchedulePanel()] @@ -28,53 +30,52 @@ XDMoD.Arr.AppKernelDashboardPanel = Ext.extend(Ext.ux.GroupTabPanel, { }, { items: [new XDMoD.Arr.WalltimePanel()] - }, - { - items: [new XDMoD.Arr.AppKerPerformanceMapPanel()] } ]; - var active_tab="appkernel_perfmap"; - var i_active_tab=0; - //var tabpanel=this.getComponent("dashboard-tabpanel"); - if(document.location.hash!==""){ - var dashboard_viewport=Ext.getCmp("dashboard_viewport"); + /* eslint-enable no-param-reassign */ + + var active_tab = 'appkernel_perfmap'; + var i_active_tab = 0; + + if (document.location.hash !== '') { var token = XDMoD.Dashboard.tokenize(document.location.hash); - if (token !== undefined && "root" in token && "tab" in token) { - active_tab=token.tab; + if (token !== undefined && 'root' in token && 'tab' in token) { + active_tab = token.tab; } } var i; - for(i=0;i0){ - if("id" in config.items[i].items[0] && config.items[i].items[0].id===active_tab){ - i_active_tab=i; + for (i = 0; i < config.items.length; i++) { + if ('items' in config.items[i] && config.items[i].items.length > 0) { + if ('id' in config.items[i].items[0] && config.items[i].items[0].id === active_tab) { + i_active_tab = i; } } } - config.activeGroup=i_active_tab; + // eslint-disable-next-line no-param-reassign + config.activeGroup = i_active_tab; XDMoD.Arr.AppKernelDashboardPanel.superclass.constructor.call(this, config); + }, // constructor: function (config) { - }, onRender: function (ct, position) { this.elements = 'body,header'; XDMoD.Arr.AppKernelDashboardPanel.superclass.onRender.call(this, ct, position); + }, // onRender: function (ct, position) { - - }, adjustBodyWidth: function (w) { return w - this.tabWidth - 2; }, + listeners: { - 'tabchange': { - fn: function (tabpanel,tab) { - var hist="appkernels:"+tab.id; - if(document.location.hash!==""){ + tabchange: { + fn: function (tabpanel, tab) { + var hist = 'appkernels:' + tab.id; + if (document.location.hash !== '') { var token = XDMoD.Dashboard.tokenize(document.location.hash); - if (token !== undefined && "root" in token && "tab" in token && "params" in token) { - if(token.tab===tab.id){ - if(token.params!==""){ - hist+="?"+token.params; + if (token !== undefined && 'root' in token && 'tab' in token && 'params' in token) { + if (token.tab === tab.id) { + if (token.params !== '') { + hist += '?' + token.params; } } } @@ -82,6 +83,6 @@ XDMoD.Arr.AppKernelDashboardPanel = Ext.extend(Ext.ux.GroupTabPanel, { Ext.History.add(hist); }, scope: this - } - } -}); + } // tabchange + } // listeners +}); // XDMoD.Arr.AppKernelDashboardPanel = Ext.extend(Ext.ux.GroupTabPanel, { diff --git a/libraries/appkernel.php b/libraries/appkernel.php index 82d691d..cd3e50e 100644 --- a/libraries/appkernel.php +++ b/libraries/appkernel.php @@ -15,8 +15,14 @@ function implode_smart($a){ } return $s; } + /** - * @return value from assosiative array @param $a for key @param $k, if key does not exists return @param $d + * Retrieve the value for $k in $a, if $a has a key $k. Else, $d is returned by default. + * @param mixed $k the key to be retrieved. + * @param array $a the array to retrieve from. + * @param null|mixed $d the default value to be returned if $k is not found. + * + * @return mixed value for $k if $k is present in $a else $d. */ function arrayValue($k,$a,$d=null) { diff --git a/xdmod-appkernels.spec.in b/xdmod-appkernels.spec.in index d9d87fa..4ccc707 100644 --- a/xdmod-appkernels.spec.in +++ b/xdmod-appkernels.spec.in @@ -61,6 +61,7 @@ rm -rf $RPM_BUILD_ROOT %config(noreplace) %{_sysconfdir}/xdmod/setup.d/appkernels*.json %config(noreplace) %{_sysconfdir}/xdmod/rest.d/akrr.json %config(noreplace) %{_sysconfdir}/xdmod/rest.d/app_kernel.json +%config(noreplace) %{_sysconfdir}/xdmod/linker.d/appkernels.json %config(noreplace) %{_sysconfdir}/cron.d/%{name} %changelog