2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-22 10:10:12 +00:00

Performance tuning

- Add `getGroupsAbridged` which returns a subset of group data for dropdowns and other places where all groups are listed
- Remove unnecessary checks for `canSeeGroup` in `groups.scala.html` since all users can see all groups
- Move `ZoneController` initialization in `manageZone.scala.html` to higher level to avoid waiting for groups to load when expanding the select box
- Add `PreparePortalHook` to automatically run `prepare-portal.sh` when `project porta; run` is executed
This commit is contained in:
Emerle, Ryan 2022-05-19 14:41:11 -04:00
parent d9f986997d
commit 02d702f461
No known key found for this signature in database
GPG Key ID: C0D34C592AED41CE
11 changed files with 130 additions and 84 deletions

View File

@ -2,10 +2,9 @@ import CompilerOptions._
import Dependencies._ import Dependencies._
import microsites._ import microsites._
import org.scalafmt.sbt.ScalafmtPlugin._ import org.scalafmt.sbt.ScalafmtPlugin._
import scoverage.ScoverageKeys.{coverageFailOnMinimum, coverageMinimum} import scoverage.ScoverageKeys.{coverageMinimum, coverageFailOnMinimum}
import scala.language.postfixOps import scala.language.postfixOps
import scala.sys.env
import scala.util.Try import scala.util.Try
lazy val IntegrationTest = config("it").extend(Test) lazy val IntegrationTest = config("it").extend(Test)
@ -38,7 +37,7 @@ lazy val sharedSettings = Seq(
// coverage options // coverage options
coverageMinimum := 85, coverageMinimum := 85,
coverageFailOnMinimum := true, coverageFailOnMinimum := true,
coverageHighlighting := true, coverageHighlighting := true
) )
lazy val testSettings = Seq( lazy val testSettings = Seq(
@ -206,9 +205,11 @@ lazy val portalSettings = Seq(
routesGenerator := InjectedRoutesGenerator, routesGenerator := InjectedRoutesGenerator,
coverageExcludedPackages := "<empty>;views.html.*;router.*;controllers\\.javascript.*;.*Reverse.*", coverageExcludedPackages := "<empty>;views.html.*;router.*;controllers\\.javascript.*;.*Reverse.*",
javaOptions in Test += "-Dconfig.file=conf/application-test.conf", javaOptions in Test += "-Dconfig.file=conf/application-test.conf",
// ads the version when working locally with sbt run // Adds the version when working locally with sbt run
PlayKeys.devSettings += "vinyldns.base-version" -> (version in ThisBuild).value, PlayKeys.devSettings += "vinyldns.base-version" -> (version in ThisBuild).value,
// adds an extra classpath to the portal loading so we can externalize jars, make sure to create the lib_extra // Automatically run the prepare portal script before `run`
PlayKeys.playRunHooks += PreparePortalHook(baseDirectory.value),
// Adds an extra classpath to the portal loading so we can externalize jars, make sure to create the lib_extra
// directory and lay down any dependencies that are required when deploying // directory and lay down any dependencies that are required when deploying
scriptClasspath in bashScriptDefines ~= (cp => cp :+ "lib_extra/*"), scriptClasspath in bashScriptDefines ~= (cp => cp :+ "lib_extra/*"),
mainClass in reStart := None, mainClass in reStart := None,
@ -241,7 +242,7 @@ lazy val portal = (project in file("modules/portal"))
.settings(testSettings) .settings(testSettings)
.settings(portalSettings) .settings(portalSettings)
.settings( .settings(
name := "portal", name := "portal"
) )
.dependsOn(mysql) .dependsOn(mysql)

View File

@ -17,8 +17,8 @@
package vinyldns.api.domain.membership package vinyldns.api.domain.membership
import java.util.UUID import java.util.UUID
import org.joda.time.DateTime import org.joda.time.DateTime
import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.core.domain.membership.GroupChangeType.GroupChangeType import vinyldns.core.domain.membership.GroupChangeType.GroupChangeType
import vinyldns.core.domain.membership.GroupStatus.GroupStatus import vinyldns.core.domain.membership.GroupStatus.GroupStatus
import vinyldns.core.domain.membership.LockStatus.LockStatus import vinyldns.core.domain.membership.LockStatus.LockStatus
@ -36,15 +36,20 @@ final case class GroupInfo(
admins: Set[UserId] = Set.empty admins: Set[UserId] = Set.empty
) )
object GroupInfo { object GroupInfo {
def apply(group: Group): GroupInfo = GroupInfo( def apply(group: Group): GroupInfo = fromGroup(group, abridged = false, None)
def fromGroup(group: Group, abridged: Boolean = false,
authPrincipal: Option[AuthPrincipal]): GroupInfo = GroupInfo(
id = group.id, id = group.id,
name = group.name, name = group.name,
email = group.email, email = group.email,
description = group.description, description = group.description,
created = group.created, created = if (abridged) null else group.created,
status = group.status, status = if (abridged) null else group.status,
members = group.memberIds.map(UserId), members = (if (abridged && authPrincipal.isDefined) group.memberIds.filter(x => authPrincipal.get.userId == x && authPrincipal.get.isGroupMember(group.id))
admins = group.adminUserIds.map(UserId) else group.memberIds).map(UserId),
admins = (if (abridged && authPrincipal.isDefined) group.adminUserIds.filter(x => authPrincipal.get.userId == x && authPrincipal.get.isGroupAdmin(group))
else group.memberIds).map(UserId)
) )
} }

View File

@ -188,7 +188,8 @@ class MembershipService(
startFrom: Option[String], startFrom: Option[String],
maxItems: Int, maxItems: Int,
authPrincipal: AuthPrincipal, authPrincipal: AuthPrincipal,
ignoreAccess: Boolean ignoreAccess: Boolean,
abridged: Boolean = false
): Result[ListMyGroupsResponse] = { ): Result[ListMyGroupsResponse] = {
val groupsCall = val groupsCall =
if (authPrincipal.isSystemAdmin || ignoreAccess) { if (authPrincipal.isSystemAdmin || ignoreAccess) {
@ -198,7 +199,7 @@ class MembershipService(
} }
groupsCall.map { grp => groupsCall.map { grp =>
pageListGroupsResponse(grp.toList, groupNameFilter, startFrom, maxItems, ignoreAccess) pageListGroupsResponse(grp.toList, groupNameFilter, startFrom, maxItems, ignoreAccess, abridged, authPrincipal)
} }
}.toResult }.toResult
@ -207,12 +208,14 @@ class MembershipService(
groupNameFilter: Option[String], groupNameFilter: Option[String],
startFrom: Option[String], startFrom: Option[String],
maxItems: Int, maxItems: Int,
ignoreAccess: Boolean ignoreAccess: Boolean,
): ListMyGroupsResponse = { abridged: Boolean = false,
authPrincipal: AuthPrincipal
): ListMyGroupsResponse = {
val allMyGroups = allGroups val allMyGroups = allGroups
.filter(_.status == GroupStatus.Active) .filter(_.status == GroupStatus.Active)
.sortBy(_.id) .sortBy(_.id)
.map(GroupInfo.apply) .map(x => GroupInfo.fromGroup(x, abridged, Some(authPrincipal)))
val filtered = allMyGroups val filtered = allMyGroups
.filter(grp => groupNameFilter.forall(grp.name.contains(_))) .filter(grp => groupNameFilter.forall(grp.name.contains(_)))

View File

@ -44,7 +44,8 @@ trait MembershipServiceAlgebra {
startFrom: Option[String], startFrom: Option[String],
maxItems: Int, maxItems: Int,
authPrincipal: AuthPrincipal, authPrincipal: AuthPrincipal,
ignoreAccess: Boolean ignoreAccess: Boolean,
abridged: Boolean = false
): Result[ListMyGroupsResponse] ): Result[ListMyGroupsResponse]
def listMembers( def listMembers(

View File

@ -82,13 +82,15 @@ class MembershipRoute(
"startFrom".?, "startFrom".?,
"maxItems".as[Int].?(DEFAULT_MAX_ITEMS), "maxItems".as[Int].?(DEFAULT_MAX_ITEMS),
"groupNameFilter".?, "groupNameFilter".?,
"ignoreAccess".as[Boolean].?(false) "ignoreAccess".as[Boolean].?(false),
"abridged".as[Boolean].?(false),
) { ) {
( (
startFrom: Option[String], startFrom: Option[String],
maxItems: Int, maxItems: Int,
groupNameFilter: Option[String], groupNameFilter: Option[String],
ignoreAccess: Boolean ignoreAccess: Boolean,
abridged: Boolean
) => ) =>
{ {
handleRejections(invalidQueryHandler) { handleRejections(invalidQueryHandler) {
@ -101,7 +103,7 @@ class MembershipRoute(
) { ) {
authenticateAndExecute( authenticateAndExecute(
membershipService membershipService
.listMyGroups(groupNameFilter, startFrom, maxItems, _, ignoreAccess) .listMyGroups(groupNameFilter, startFrom, maxItems, _, ignoreAccess, abridged)
) { groups => ) { groups =>
complete(StatusCodes.OK, groups) complete(StatusCodes.OK, groups)
} }

View File

@ -79,8 +79,7 @@
<tbody> <tbody>
<tr ng-repeat="group in groups.items | orderBy:'+name'"> <tr ng-repeat="group in groups.items | orderBy:'+name'">
<td class="wrap-long-text"> <td class="wrap-long-text">
<a ng-if="canSeeGroup(group)" ng-href="/groups/{{group.id}}">{{group.name}}</a> <a ng-href="/groups/{{group.id}}">{{group.name}}</a>
<span ng-if="!canSeeGroup(group)">{{group.name}}</span>
</td> </td>
<td class="wrap-long-text">{{group.email}}</td> <td class="wrap-long-text">{{group.email}}</td>
<td class="wrap-long-text">{{group.description}}</td> <td class="wrap-long-text">{{group.description}}</td>

View File

@ -352,7 +352,7 @@
<!-- START ACL MODAL --> <!-- START ACL MODAL -->
<form name="addAclRuleForm" role="form" class="form-horizontal" novalidate> <form name="addAclRuleForm" role="form" class="form-horizontal" novalidate>
<modal modal-id="acl_modal" modal-title="{{ aclModal.title }}"> <modal modal-id="acl_modal" modal-title="{{ aclModal.title }}">
<div class="modal-body"> <div class="modal-body" ng-controller="ZonesController">
<modal-element label="Apply Rule to" invalid-when="addAclRuleForm.$submitted && addAclRuleForm.priority.$invalid"> <modal-element label="Apply Rule to" invalid-when="addAclRuleForm.$submitted && addAclRuleForm.priority.$invalid">
<select name="priority" <select name="priority"
id="acl-rule-priority" id="acl-rule-priority"
@ -373,10 +373,9 @@
<select name="groupId" <select name="groupId"
class="form-control" class="form-control"
ng-model="currentAclRule.groupId" ng-model="currentAclRule.groupId"
ng-controller="ZonesController"
ng-disabled="aclModal.details.readOnly" ng-disabled="aclModal.details.readOnly"
required> required>
<option ng-repeat="group in allGroups" value="{{ group.id }}"> <option ng-repeat="group in allGroups | orderBy:'+name'" value="{{ group.id }}">
{{group.name}} ({{group.description}})</option> {{group.name}} ({{group.description}})</option>
<option value="">--- Please select a group ---</option> <option value="">--- Please select a group ---</option>
</select> </select>

View File

@ -15,12 +15,12 @@
*/ */
angular.module('controller.groups', []).controller('GroupsController', function ($scope, $log, $location, groupsService, profileService, utilityService) { angular.module('controller.groups', []).controller('GroupsController', function ($scope, $log, $location, groupsService, profileService, utilityService) {
//registering bootstrap modal close event to refresh data after create group action //registering bootstrap modal close event to refresh data after create group action
angular.element('#modal_new_group').one('hide.bs.modal', function () { angular.element('#modal_new_group').one('hide.bs.modal', function () {
$scope.closeModal(); $scope.closeModal();
}); });
$scope.groups = { items: [] }; $scope.groups = {items: []};
$scope.groupsLoaded = false; $scope.groupsLoaded = false;
$scope.alerts = []; $scope.alerts = [];
$scope.ignoreAccess = false; $scope.ignoreAccess = false;
@ -37,26 +37,26 @@ angular.module('controller.groups', []).controller('GroupsController', function
//shared modal //shared modal
var modalDialog; var modalDialog;
$scope.openModal = function(evt){ $scope.openModal = function (evt) {
$scope.currentGroup = {}; $scope.currentGroup = {};
void(evt && evt.preventDefault()); void (evt && evt.preventDefault());
if(!modalDialog){ if (!modalDialog) {
modalDialog = angular.element('#modal_new_group').modal(); modalDialog = angular.element('#modal_new_group').modal();
} }
modalDialog.modal('show'); modalDialog.modal('show');
}; };
$scope.closeModal = function(evt){ $scope.closeModal = function (evt) {
void(evt && evt.preventDefault()); void (evt && evt.preventDefault());
if(!modalDialog){ if (!modalDialog) {
modalDialog = angular.element('#modal_new_group').modal(); modalDialog = angular.element('#modal_new_group').modal();
} }
modalDialog.modal('hide'); modalDialog.modal('hide');
return true; return true;
}; };
$scope.closeEditModal = function(evt){ $scope.closeEditModal = function (evt) {
void(evt && evt.preventDefault()); void (evt && evt.preventDefault());
editModalDialog = angular.element('#modal_edit_group').modal(); editModalDialog = angular.element('#modal_edit_group').modal();
editModalDialog.modal('hide'); editModalDialog.modal('hide');
$scope.reset(); $scope.reset();
@ -68,7 +68,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
//prevent user executing service call multiple times //prevent user executing service call multiple times
//if true prevent, if false allow for execution of rest of code //if true prevent, if false allow for execution of rest of code
//ng-href='/groups' //ng-href='/groups'
$log.log('createGroup::called', $scope.data); $log.log('createGroup::called', $scope.data);
if ($scope.processing) { if ($scope.processing) {
$log.log('createGroup::processing is true; exiting'); $log.log('createGroup::processing is true; exiting');
@ -83,14 +83,14 @@ angular.module('controller.groups', []).controller('GroupsController', function
'name': name, 'name': name,
'email': email, 'email': email,
'description': description, 'description': description,
'members': [{ id: $scope.profile.id }], 'members': [{id: $scope.profile.id}],
'admins': [{ id: $scope.profile.id }] 'admins': [{id: $scope.profile.id}]
}; };
//create group success callback //create group success callback
function success(response) { function success(response) {
var alert = utilityService.success('Successfully Created Group: ' + name, response, 'createGroup::createGroup successful'); var alert = utilityService.success('Successfully Created Group: ' + name, response, 'createGroup::createGroup successful');
$scope.alerts.push(alert); $scope.alerts.push(alert);
$scope.closeModal(); $scope.closeModal();
$scope.reset(); $scope.reset();
$scope.refresh(); $scope.refresh();
@ -99,17 +99,17 @@ angular.module('controller.groups', []).controller('GroupsController', function
return groupsService.createGroup(payload) return groupsService.createGroup(payload)
.then(success) .then(success)
.catch(function (error){ .catch(function (error) {
handleError(error, 'groupsService::createGroup-failure'); handleError(error, 'groupsService::createGroup-failure');
}); });
}; };
$scope.allGroups = function() { $scope.allGroups = function () {
$scope.ignoreAccess = true; $scope.ignoreAccess = true;
$scope.refresh(); $scope.refresh();
} }
$scope.myGroups = function() { $scope.myGroups = function () {
$scope.ignoreAccess = false; $scope.ignoreAccess = false;
$scope.refresh(); $scope.refresh();
} }
@ -121,11 +121,12 @@ angular.module('controller.groups', []).controller('GroupsController', function
$scope.groups.items = result.groups; $scope.groups.items = result.groups;
$scope.groupsLoaded = true; $scope.groupsLoaded = true;
if (!$scope.query.length) { if (!$scope.query.length) {
$scope.hasGroups = response.data.groups.length > 0; $scope.hasGroups = $scope.groups.items.length > 0;
} }
return result; return result;
} }
getGroups($scope.ignoreAccess)
getGroupsAbridged($scope.ignoreAccess)
.then(success) .then(success)
.catch(function (error) { .catch(function (error) {
handleError(error, 'getGroups::refresh-failure'); handleError(error, 'getGroups::refresh-failure');
@ -151,30 +152,43 @@ angular.module('controller.groups', []).controller('GroupsController', function
$log.log('groupsService::getGroups-success'); $log.log('groupsService::getGroups-success');
return response.data; return response.data;
} }
return groupsService return groupsService
.getGroups($scope.ignoreAccess, $scope.query) .getGroups($scope.ignoreAccess, $scope.query)
.then(success) .then(success)
.catch(function (error){ .catch(function (error) {
handleError(error, 'groupsService::getGroups-failure'); handleError(error, 'groupsService::getGroups-failure');
}); });
}
function getGroupsAbridged() {
function success(response) {
$log.log('groupsService::getGroups-success');
return response.data;
}
return groupsService
.getGroupsAbridged($scope.ignoreAccess, $scope.query)
.then(success)
.catch(function (error) {
handleError(error, 'groupsService::getGroups-failure');
});
} }
// Return true if there are no groups created by the user // Return true if there are no groups created by the user
$scope.haveNoGroups = function(groupLength){ $scope.haveNoGroups = function (groupLength) {
if(!$scope.hasGroups && !groupLength && $scope.groupsLoaded && $scope.query.length == ""){ if (!$scope.hasGroups && !groupLength && $scope.groupsLoaded && $scope.query.length == "") {
return true return true
} } else {
else{
return false return false
} }
} }
// Return true if no groups are found related to the search query // Return true if no groups are found related to the search query
$scope.searchCriteria = function(groupLength){ $scope.searchCriteria = function (groupLength) {
if($scope.groupsLoaded && !groupLength && $scope.query.length != ""){ if ($scope.groupsLoaded && !groupLength && $scope.query.length != "") {
return true return true
} } else {
else{
return false return false
} }
} }
@ -188,7 +202,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
//prevent user executing service call multiple times //prevent user executing service call multiple times
//if true prevent, if false allow for execution of rest of code //if true prevent, if false allow for execution of rest of code
//ng-href='/groups' //ng-href='/groups'
$log.log('updateGroup::called', $scope.data); $log.log('updateGroup::called', $scope.data);
if ($scope.processing) { if ($scope.processing) {
$log.log('updateGroup::processing is true; exiting'); $log.log('updateGroup::processing is true; exiting');
@ -213,17 +227,17 @@ angular.module('controller.groups', []).controller('GroupsController', function
//update group success callback //update group success callback
function success(response) { function success(response) {
var alert = utilityService.success('Successfully Updated Group: ' + name, response, 'updateGroup::updateGroup successful'); var alert = utilityService.success('Successfully Updated Group: ' + name, response, 'updateGroup::updateGroup successful');
$scope.alerts.push(alert); $scope.alerts.push(alert);
$scope.closeEditModal(); $scope.closeEditModal();
$scope.reset(); $scope.reset();
$scope.refresh(); $scope.refresh();
return response.data; return response.data;
} }
return groupsService.updateGroup($scope.currentGroup.id, payload) return groupsService.updateGroup($scope.currentGroup.id, payload)
.then(success) .then(success)
.catch(function (error){ .catch(function (error) {
handleError(error, 'groupsService::updateGroup-failure'); handleError(error, 'groupsService::updateGroup-failure');
}); });
}; };
@ -234,17 +248,18 @@ angular.module('controller.groups', []).controller('GroupsController', function
}; };
$scope.submitDeleteGroup = function () { $scope.submitDeleteGroup = function () {
function success (response){ function success(response) {
$("#delete_group_modal").modal("hide"); $("#delete_group_modal").modal("hide");
$scope.refresh(); $scope.refresh();
var alert = utilityService.success('Removed Group: ' + $scope.currentGroup.name, response, 'groupsService::deleteGroup successful'); var alert = utilityService.success('Removed Group: ' + $scope.currentGroup.name, response, 'groupsService::deleteGroup successful');
$scope.alerts.push(alert); $scope.alerts.push(alert);
} }
groupsService.deleteGroups($scope.currentGroup.id) groupsService.deleteGroups($scope.currentGroup.id)
.then(success) .then(success)
.catch(function (error){ .catch(function (error) {
handleError(error, 'groupsService::deleteGroup-failure'); handleError(error, 'groupsService::deleteGroup-failure');
}); });
}; };
function profileSuccess(results) { function profileSuccess(results) {
@ -263,15 +278,15 @@ angular.module('controller.groups', []).controller('GroupsController', function
$scope.profile = $scope.profile || {}; $scope.profile = $scope.profile || {};
} }
$scope.groupAdmin = function(group) { $scope.groupAdmin = function (group) {
var isAdmin = group.admins.find(function (x) { var isAdmin = group.admins.find(function (x) {
return x.id === $scope.profile.id; return x.id === $scope.profile.id;
}); });
var isSuper = $scope.profile.isSuper; var isSuper = $scope.profile.isSuper;
return isAdmin || isSuper; return isAdmin || isSuper;
} }
$scope.canSeeGroup = function(group) { $scope.canSeeGroup = function (group) {
var isMember = group.members.some(x => x.id === $scope.profile.id); var isMember = group.members.some(x => x.id === $scope.profile.id);
var isSupport = $scope.profile.isSupport; var isSupport = $scope.profile.isSupport;
var isSuper = $scope.profile.isSuper; var isSuper = $scope.profile.isSuper;

View File

@ -53,15 +53,7 @@ angular.module('controller.zones', [])
$scope.currentZone.transferConnection = {}; $scope.currentZone.transferConnection = {};
}; };
groupsService.getGroups().then(function (results) { groupsService.getGroupsAbridged(true, "").then(function (results) {
if (results.data) {
$scope.myGroups = results.data.groups;
$scope.myGroupIds = results.data.groups.map(function(grp) {return grp['id']});
}
$scope.resetCurrentZone();
});
groupsService.getGroups(true, "").then(function (results) {
if (results.data) { if (results.data) {
$scope.allGroups = results.data.groups; $scope.allGroups = results.data.groups;
} }

View File

@ -84,6 +84,21 @@ angular.module('service.groups', [])
return $http.get(url); return $http.get(url);
}; };
this.getGroupsAbridged = function (ignoreAccess, query) {
if (query == "") {
query = null;
}
var params = {
"maxItems": 1500,
"groupNameFilter": query,
"ignoreAccess": ignoreAccess,
"abridged": true
};
var url = '/api/groups';
url = this.urlBuilder(url, params);
return $http.get(url);
};
this.getGroupListChanges = function (id, count, groupId) { this.getGroupListChanges = function (id, count, groupId) {
var url = '/api/groups/' + groupId + '/changes'; var url = '/api/groups/' + groupId + '/changes';
url = this.urlBuilder(url, { 'startFrom': id, 'maxItems': count }); url = this.urlBuilder(url, { 'startFrom': id, 'maxItems': count });

View File

@ -0,0 +1,14 @@
import scala.sys.process.Process
import play.sbt.PlayRunHook
import sbt.File
object PreparePortalHook {
def apply(base: File): PlayRunHook = {
object GruntProcess extends PlayRunHook {
override def beforeStarted(): Unit = {
Process("./prepare-portal.sh", base).!
}
}
GruntProcess
}
}