This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 063eb10  Adds a more powerful UI grid for changelogs and removes 
changelog entry for unqueuing servers updates/revals (#5329)
063eb10 is described below

commit 063eb105d2a86b58e6543f29dbc3bb4c1ec0d9ff
Author: Jeremy Mitchell <[email protected]>
AuthorDate: Fri Nov 27 11:14:10 2020 -0700

    Adds a more powerful UI grid for changelogs and removes changelog entry for 
unqueuing servers updates/revals (#5329)
    
    * swaps jquery datatable for ag-grid for change logs table
    
    * the number of change log days that TP fetches is now configurable
    
    * changes default to 7 days of change logs that TP fetches
    
    * removes changelog message created on server update/reval unqueue
    
    * add changelog entry
    
    * shows how many days of change logs are being displayed
    
    * allows user to specify type of input dialog
    
    * properties file should only be loaded once
    
    * allows the user to specify the number of days of change logs to retrieve.
    
    * enforces a min and max on number of days requested
    
    * updates CHANGELOG.md
    
    * updates per PR review
---
 CHANGELOG.md                                       |   5 +
 traffic_ops/traffic_ops_golang/server/update.go    |  14 --
 .../app/src/common/models/PropertiesModel.js       |   1 +
 .../modules/dialog/input/dialog.input.tpl.html     |   2 +-
 .../table/changeLogs/TableChangeLogsController.js  | 243 ++++++++++++++++++++-
 .../table/changeLogs/table.changeLogs.tpl.html     |  60 ++---
 .../src/modules/private/changeLogs/list/index.js   |   8 +-
 .../app/src/traffic_portal_properties.json         |   4 +
 8 files changed, 285 insertions(+), 52 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30ccb47..8d25492 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,9 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - Traffic Ops: added validation for topology updates and server 
updates/deletions to ensure that topologies have at least one server per 
cachegroup in each CDN of any assigned delivery services
 - Traffic Ops: added validation for delivery service updates to ensure that 
topologies have at least one server per cachegroup in each CDN of any assigned 
delivery services
 - Added locationByDeepCoverageZone to the `crs/stats/ip/{ip}` endpoint in the 
Traffic Router API
+- Traffic Portal: upgraded change log UI table to use more powerful/performant 
ag-grid component
+- Traffic Portal: change log days are now configurable in 
traffic_portal_properties.json (default is 7 days) and can be overridden by the 
user in TP
+
 
 ### Fixed
 - [#5274](https://github.com/apache/trafficcontrol/issues/5274) - CDN in a 
Box's Traffic Vault image failed to build due 
@@ -20,6 +23,8 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Changed
 - Updated CDN in a Box to CentOS 8 and added `CENTOS_VERSION` Docker build arg 
so CDN in a Box can be built for CentOS 7, if desired
+- Traffic Ops: removed change log entry created during server 
update/revalidation unqueue
+
 
 ## [5.0.0] - 2020-10-20
 ### Added
diff --git a/traffic_ops/traffic_ops_golang/server/update.go 
b/traffic_ops/traffic_ops_golang/server/update.go
index 21fcd63..a4e15c1 100644
--- a/traffic_ops/traffic_ops_golang/server/update.go
+++ b/traffic_ops/traffic_ops_golang/server/update.go
@@ -102,20 +102,6 @@ func UpdateHandler(w http.ResponseWriter, r *http.Request) 
{
                return
        }
 
-       err = api.CreateChangeLogBuildMsg(
-               api.ApiChange,
-               api.Updated,
-               inf.User,
-               inf.Tx.Tx,
-               "server-update-status",
-               hostName,
-               map[string]interface{}{"host_name": hostName},
-       )
-       if err != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("writing changelog: "+err.Error()))
-               return
-       }
-
        respMsg := "successfully set server '" + hostName + "'"
        if hasUpdated {
                respMsg += " updated=" + strconv.FormatBool(strToBool(updated))
diff --git a/traffic_portal/app/src/common/models/PropertiesModel.js 
b/traffic_portal/app/src/common/models/PropertiesModel.js
index 5720a18..b1326ca 100644
--- a/traffic_portal/app/src/common/models/PropertiesModel.js
+++ b/traffic_portal/app/src/common/models/PropertiesModel.js
@@ -21,6 +21,7 @@ var PropertiesModel = function() {
        this.loaded = false;
 
        this.setProperties = function(properties) {
+               if (this.loaded) return;
                this.properties = properties;
                this.loaded = true;
        };
diff --git 
a/traffic_portal/app/src/common/modules/dialog/input/dialog.input.tpl.html 
b/traffic_portal/app/src/common/modules/dialog/input/dialog.input.tpl.html
index 35b5346..19bb68a 100644
--- a/traffic_portal/app/src/common/modules/dialog/input/dialog.input.tpl.html
+++ b/traffic_portal/app/src/common/modules/dialog/input/dialog.input.tpl.html
@@ -24,7 +24,7 @@ under the License.
 <div class="modal-body">
     <p ng-bind-html="params.message"></p>
     <form name="inputForm" novalidate>
-        <input type="text" class="form-control" ng-model="inputValue" 
ng-maxlength="256">
+        <input type="{{params.type ? params.type : 'text'}}" 
class="form-control" ng-model="inputValue" ng-maxlength="256">
     </form>
 </div>
 <div class="modal-footer">
diff --git 
a/traffic_portal/app/src/common/modules/table/changeLogs/TableChangeLogsController.js
 
b/traffic_portal/app/src/common/modules/table/changeLogs/TableChangeLogsController.js
index d8281df..59900f7 100644
--- 
a/traffic_portal/app/src/common/modules/table/changeLogs/TableChangeLogsController.js
+++ 
b/traffic_portal/app/src/common/modules/table/changeLogs/TableChangeLogsController.js
@@ -17,25 +17,248 @@
  * under the License.
  */
 
-var TableChangeLogsController = function(changeLogs, $scope, $state, 
dateUtils) {
+var TableChangeLogsController = function(tableName, changeLogs, $scope, 
$state, $uibModal, dateUtils, propertiesModel, messageModel) {
 
-       $scope.changeLogs = changeLogs;
+       /**
+        * Gets value to display a default tooltip.
+        */
+       function defaultTooltip(params) {
+               return params.value;
+       }
 
-       $scope.getRelativeTime = dateUtils.getRelativeTime;
+       /**
+        * Formats the contents of a 'lastUpdated' column cell as "relative to 
now".
+        */
+       function dateCellFormatterRelative(params) {
+               return params.value ? dateUtils.getRelativeTime(params.value) : 
params.value;
+       }
 
-       $scope.refresh = function() {
-               $state.reload(); // reloads all the resolves for the view
+       function dateCellFormatter(params) {
+               return params.value.toUTCString();
+       }
+
+       let columns = [
+               {
+                       headerName: "Occurred",
+                       field: "lastUpdated",
+                       hide: false,
+                       filter: "agDateColumnFilter",
+                       tooltip: dateCellFormatterRelative,
+                       valueFormatter: dateCellFormatterRelative
+               },
+               {
+                       headerName: "Created (UTC)",
+                       field: "lastUpdated",
+                       hide: false,
+                       filter: "agDateColumnFilter",
+                       tooltip: dateCellFormatter,
+                       valueFormatter: dateCellFormatter
+               },
+               {
+                       headerName: "User",
+                       field: "user",
+                       hide: false
+               },
+               {
+                       headerName: "Level",
+                       field: "level",
+                       hide: true
+               },
+               {
+                       headerName: "Message",
+                       field: "message",
+                       hide: false
+               }
+       ];
+
+       $scope.days = (propertiesModel.properties.changeLogs) ? 
propertiesModel.properties.changeLogs.days : 7;
+
+       /** All of the change logs - lastUpdated fields converted to actual 
Date */
+       $scope.changeLogs = changeLogs.map(
+               function(x) {
+                       x.lastUpdated = x.lastUpdated ? new 
Date(x.lastUpdated.replace("+00", "Z")) : x.lastUpdated;
+               });
+
+       $scope.quickSearch = '';
+
+       $scope.pageSize = 100;
+
+       /** Options, configuration, data and callbacks for the ag-grid table. */
+       $scope.gridOptions = {
+               columnDefs: columns,
+               enableCellTextSelection: true,
+               suppressMenuHide: true,
+               multiSortKey: 'ctrl',
+               alwaysShowVerticalScroll: true,
+               defaultColDef: {
+                       filter: true,
+                       sortable: true,
+                       resizable: true,
+                       tooltip: defaultTooltip
+               },
+               rowData: changeLogs,
+               pagination: true,
+               paginationPageSize: $scope.pageSize,
+               rowBuffer: 0,
+               onColumnResized: function(params) {
+                       localStorage.setItem(tableName + "_table_columns", 
JSON.stringify($scope.gridOptions.columnApi.getColumnState()));
+               },
+               tooltipShowDelay: 500,
+               allowContextMenuWithControlKey: true,
+               preventDefaultOnContextMenu: true,
+               onColumnVisible: function(params) {
+                       if (params.visible){
+                               return;
+                       }
+                       for (let column of params.columns) {
+                               if (column.filterActive) {
+                                       const filterModel = 
$scope.gridOptions.api.getFilterModel();
+                                       if (column.colId in filterModel) {
+                                               delete 
filterModel[column.colId];
+                                               
$scope.gridOptions.api.setFilterModel(filterModel);
+                                       }
+                               }
+                       }
+               },
+               colResizeDefault: "shift"
+       };
+
+       /** Allows the user to change the number of days queried for change 
logs. */
+       $scope.changeDays = function() {
+               const params = {
+                       title: 'Change Number of Days',
+                       message: 'Enter the number of days of change logs you 
need access to (between 1 and 365).',
+                       type: 'number'
+               };
+               const modalInstance = $uibModal.open({
+                       templateUrl: 
'common/modules/dialog/input/dialog.input.tpl.html',
+                       controller: 'DialogInputController',
+                       size: 'md',
+                       resolve: {
+                               params: function () {
+                                       return params;
+                               }
+                       }
+               });
+               modalInstance.result.then(function(days) {
+                       let numOfDays = parseInt(days, 10);
+                       if (numOfDays >= 1 && numOfDays <= 365) {
+                               propertiesModel.properties.changeLogs.days = 
numOfDays;
+                               $state.reload();
+                       } else {
+                               messageModel.setMessages([{level: 'error', 
text: 'Number of days must be between 1 and 365' }], false);
+                       }
+               }, function () {
+                       console.log('Cancelled');
+               });
+       };
+
+       /** Toggles the visibility of a column that has the ID provided as 
'col'. */
+       $scope.toggleVisibility = function(col) {
+               const visible = 
$scope.gridOptions.columnApi.getColumn(col).isVisible();
+               $scope.gridOptions.columnApi.setColumnVisible(col, !visible);
+       };
+
+       /** Downloads the table as a CSV */
+       $scope.exportCSV = function() {
+               const params = {
+                       allColumns: true,
+                       fileName: "change_logs.csv",
+               };
+               $scope.gridOptions.api.exportDataAsCsv(params);
+       }
+
+       $scope.onQuickSearchChanged = function() {
+               $scope.gridOptions.api.setQuickFilter($scope.quickSearch);
+               localStorage.setItem(tableName + "_quick_search", 
$scope.quickSearch);
        };
 
+       $scope.onPageSizeChanged = function() {
+               const value = Number($scope.pageSize);
+               $scope.gridOptions.api.paginationSetPageSize(value);
+               localStorage.setItem(tableName + "_page_size", value);
+       };
+
+       $scope.clearTableFilters = function() {
+               // clear the quick search
+               $scope.quickSearch = '';
+               $scope.onQuickSearchChanged();
+               // clear any column filters
+               $scope.gridOptions.api.setFilterModel(null);
+       };
+
+       /**** Initialization code, including loading user columns from 
localstorage ****/
        angular.element(document).ready(function () {
-               $('#changeLogsTable').dataTable({
-                       "aLengthMenu": [[25, 50, 100, -1], [25, 50, 100, 
"All"]],
-                       "iDisplayLength": 25,
-                       "aaSorting": []
+               try {
+                       // need to create the show/hide column checkboxes and 
bind to the current visibility
+                       const colstates = 
JSON.parse(localStorage.getItem(tableName + "_table_columns"));
+                       if (colstates) {
+                               if 
(!$scope.gridOptions.columnApi.setColumnState(colstates)) {
+                                       console.error("Failed to load stored 
column state: one or more columns not found");
+                               }
+                       } else {
+                               $scope.gridOptions.api.sizeColumnsToFit();
+                       }
+               } catch (e) {
+                       console.error("Failure to retrieve required column info 
from localStorage (key=" + tableName + "_table_columns):", e);
+               }
+
+               try {
+                       const filterState = 
JSON.parse(localStorage.getItem(tableName + "_table_filters")) || {};
+                       $scope.gridOptions.api.setFilterModel(filterState);
+               } catch (e) {
+                       console.error("Failure to load stored filter state:", 
e);
+               }
+
+               $scope.gridOptions.api.addEventListener("filterChanged", 
function() {
+                       localStorage.setItem(tableName + "_table_filters", 
JSON.stringify($scope.gridOptions.api.getFilterModel()));
+               });
+
+               try {
+                       const sortState = 
JSON.parse(localStorage.getItem(tableName + "_table_sort"));
+                       $scope.gridOptions.api.setSortModel(sortState);
+               } catch (e) {
+                       console.error("Failure to load stored sort state:", e);
+               }
+
+               try {
+                       $scope.quickSearch = localStorage.getItem(tableName + 
"_quick_search");
+                       
$scope.gridOptions.api.setQuickFilter($scope.quickSearch);
+               } catch (e) {
+                       console.error("Failure to load stored quick search:", 
e);
+               }
+
+               try {
+                       const ps = localStorage.getItem(tableName + 
"_page_size");
+                       if (ps && ps > 0) {
+                               $scope.pageSize = Number(ps);
+                               
$scope.gridOptions.api.paginationSetPageSize($scope.pageSize);
+                       }
+               } catch (e) {
+                       console.error("Failure to load stored page size:", e);
+               }
+
+               $scope.gridOptions.api.addEventListener("sortChanged", 
function() {
+                       localStorage.setItem(tableName + "_table_sort", 
JSON.stringify($scope.gridOptions.api.getSortModel()));
+               });
+
+               $scope.gridOptions.api.addEventListener("columnMoved", 
function() {
+                       localStorage.setItem(tableName + "_table_columns", 
JSON.stringify($scope.gridOptions.columnApi.getColumnState()));
+               });
+
+               $scope.gridOptions.api.addEventListener("columnVisible", 
function() {
+                       $scope.gridOptions.api.sizeColumnsToFit();
+                       try {
+                               colStates = 
$scope.gridOptions.columnApi.getColumnState();
+                               localStorage.setItem(tableName + 
"_table_columns", JSON.stringify(colStates));
+                       } catch (e) {
+                               console.error("Failed to store column defs to 
local storage:", e);
+                       }
                });
+
        });
 
 };
 
-TableChangeLogsController.$inject = ['changeLogs', '$scope', '$state', 
'dateUtils'];
+TableChangeLogsController.$inject = ['tableName', 'changeLogs', '$scope', 
'$state', '$uibModal', 'dateUtils', 'propertiesModel', 'messageModel'];
 module.exports = TableChangeLogsController;
diff --git 
a/traffic_portal/app/src/common/modules/table/changeLogs/table.changeLogs.tpl.html
 
b/traffic_portal/app/src/common/modules/table/changeLogs/table.changeLogs.tpl.html
index 951801e..4c6f311 100644
--- 
a/traffic_portal/app/src/common/modules/table/changeLogs/table.changeLogs.tpl.html
+++ 
b/traffic_portal/app/src/common/modules/table/changeLogs/table.changeLogs.tpl.html
@@ -20,35 +20,45 @@ under the License.
 <div class="x_panel">
     <div class="x_title">
         <ol class="breadcrumb pull-left">
-            <li class="active">Change Logs</li>
+            <li class="active">Change Logs <button type="button" class="btn 
btn-link" ng-click="changeDays()">[ last {{days}} days ]</button></li>
         </ol>
-        <div class="pull-right" role="group">
-            <button class="btn btn-default" title="Refresh" 
ng-click="refresh()"><i class="fa fa-refresh"></i></button>
+        <div class="pull-right">
+            <div class="form-inline" role="search">
+                <input id="quickSearch" name="quickSearch" type="search" 
class="form-control text-input" placeholder="Quick search..." 
ng-model="quickSearch" ng-change="onQuickSearchChanged()" aria-label="Search"/>
+                <div class="input-group text-input">
+                    <span class="input-group-addon">
+                        <label for="pageSize">Page size</label>
+                    </span>
+                    <input id="pageSize" name="pageSize" type="number" 
class="form-control" placeholder="100" ng-model="pageSize" 
ng-change="onPageSizeChanged()" />
+                </div>
+                <div id="toggleColumns" class="btn-group" role="group" 
title="Select Table Columns" uib-dropdown is-open="columnSettings.isopen">
+                    <button type="button" class="btn btn-default 
dropdown-toggle" uib-dropdown-toggle aria-haspopup="true" aria-expanded="false">
+                        <i class="fa fa-columns"></i>&nbsp;
+                        <span class="caret"></span>
+                    </button>
+                    <menu ng-click="$event.stopPropagation()" 
class="column-settings dropdown-menu-right dropdown-menu" uib-dropdown-menu>
+                        <li role="menuitem" ng-repeat="c in 
gridOptions.columnApi.getAllColumns() | orderBy:'colDef.headerName'">
+                            <div class="checkbox">
+                                <label><input type="checkbox" 
ng-checked="c.isVisible()" 
ng-click="toggleVisibility(c.colId)">{{::c.colDef.headerName}}</label>
+                            </div>
+                        </li>
+                    </menu>
+                </div>
+                <div class="btn-group" role="group" uib-dropdown 
is-open="more.isopen">
+                    <button name="moreBtn" type="button" class="btn 
btn-default dropdown-toggle" uib-dropdown-toggle aria-haspopup="true" 
aria-expanded="false">
+                        More&nbsp;
+                        <span class="caret"></span>
+                    </button>
+                    <ul class="dropdown-menu-right dropdown-menu" 
uib-dropdown-menu>
+                        <li role="menuitem"><button class="menu-item-button" 
type="button" ng-click="clearTableFilters()">Clear Table Filters</button></li>
+                        <li role="menuitem"><button class="menu-item-button" 
type="button" ng-click="exportCSV()">Export CSV</button></li>
+                    </ul>
+                </div>
+            </div>
         </div>
         <div class="clearfix"></div>
     </div>
     <div class="x_content">
-        <br>
-        <table id="changeLogsTable" class="table responsive-utilities 
jambo_table">
-            <thead>
-            <tr class="headings">
-                <th>Occurred</th>
-                <th>Timestamp (UTC)</th>
-                <th>User</th>
-                <th>Type</th>
-                <th>Message</th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr ng-repeat="c in ::changeLogs">
-                <td data-search="^{{::getRelativeTime(c.lastUpdated)}}$" 
data-order="{{::c.lastUpdated}}">{{::getRelativeTime(c.lastUpdated)}}</td>
-                <td 
data-search="^{{::c.lastUpdated}}$">{{::c.lastUpdated}}</td>
-                <td data-search="^{{::c.user}}$">{{::c.user}}</td>
-                <td data-search="^{{::c.level}}$">{{::c.level}}</td>
-                <td data-search="^{{::c.message}}$">{{::c.message}}</td>
-            </tr>
-            </tbody>
-        </table>
+        <div style="height: 740px;" ag-grid="gridOptions" 
class="change-logs-table ag-theme-alpine"></div>
     </div>
 </div>
-
diff --git a/traffic_portal/app/src/modules/private/changeLogs/list/index.js 
b/traffic_portal/app/src/modules/private/changeLogs/list/index.js
index 7279939..f25f143 100644
--- a/traffic_portal/app/src/modules/private/changeLogs/list/index.js
+++ b/traffic_portal/app/src/modules/private/changeLogs/list/index.js
@@ -27,8 +27,12 @@ module.exports = 
angular.module('trafficPortal.private.changeLogs.list', [])
                                                templateUrl: 
'common/modules/table/changeLogs/table.changeLogs.tpl.html',
                                                controller: 
'TableChangeLogsController',
                                                resolve: {
-                                                       changeLogs: 
function(changeLogService) {
-                                                               return 
changeLogService.getChangeLogs({ days: 3 });
+                                                       tableName: function() {
+                                                               return 
'changeLogs';
+                                                       },
+                                                       changeLogs: 
function(changeLogService, propertiesModel) {
+                                                               const days = 
(propertiesModel.properties.changeLogs) ? 
propertiesModel.properties.changeLogs.days : 7;
+                                                               return 
changeLogService.getChangeLogs({ days: days });
                                                        }
                                                }
                                        }
diff --git a/traffic_portal/app/src/traffic_portal_properties.json 
b/traffic_portal/app/src/traffic_portal_properties.json
index 770ba91..3766f42 100644
--- a/traffic_portal/app/src/traffic_portal_properties.json
+++ b/traffic_portal/app/src/traffic_portal_properties.json
@@ -203,6 +203,10 @@
         "baseUrl": 
"https://trafficstats.domain.com/dashboard/script/traffic_ops_server.js?which=";
       }
     },
+    "changeLogs": {
+      "_comment": "Change log settings",
+      "days": 7
+    },
     "customMenu": {
       "_comments": "These are custom items you want to add to the menu. 
'items' is an array of hashes where each hash has 'name' (the menu item name), 
'embed' (true|false to determine if content is embedded in TP or not), and 
'url' (the url of the content)",
       "name": "Other",

Reply via email to