You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2020/11/30 18:18:55 UTC
[trafficcontrol] branch 5.0.x updated: Adds a more powerful UI grid
for changelogs and removes changelog entry for unqueuing servers
updates/revals (#5329)
This is an automated email from the ASF dual-hosted git repository.
ocket8888 pushed a commit to branch 5.0.x
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/5.0.x by this push:
new f5d305d Adds a more powerful UI grid for changelogs and removes changelog entry for unqueuing servers updates/revals (#5329)
f5d305d is described below
commit f5d305d03c1f00c9ee759fd91231267a45bb0d3d
Author: Jeremy Mitchell <mi...@users.noreply.github.com>
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
(cherry picked from commit 063eb105d2a86b58e6543f29dbc3bb4c1ec0d9ff)
---
CHANGELOG.md | 3 +
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, 283 insertions(+), 52 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ede1bf2..28bfae9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -70,6 +70,8 @@ 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
- Traffic Ops: added a feature to get delivery services filtered by the `active` flag
+- 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
- Fixed #5188 - DSR (delivery service request) incorrectly marked as complete and error message not displaying when DSR fulfilled and DS update fails in Traffic Portal. [Related Github issue](https://github.com/apache/trafficcontrol/issues/5188)
@@ -162,6 +164,7 @@ will be returned indicating that overlap exists.
- Changed deprecated AsyncHttpClient Java dependency to use new active mirror and updated to version 2.12.1.
- Changed Traffic Portal to use the more performant and powerful ag-grid for the delivery service request (DSR) table.
- 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
### Deprecated
- Deprecated the non-nullable `DeliveryService` Go struct and other structs that use it. `DeliveryServiceNullable` structs should be used instead.
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>
+ <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
+ <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",