2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-22 02:02:14 +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 microsites._
import org.scalafmt.sbt.ScalafmtPlugin._
import scoverage.ScoverageKeys.{coverageFailOnMinimum, coverageMinimum}
import scoverage.ScoverageKeys.{coverageMinimum, coverageFailOnMinimum}
import scala.language.postfixOps
import scala.sys.env
import scala.util.Try
lazy val IntegrationTest = config("it").extend(Test)
@ -38,7 +37,7 @@ lazy val sharedSettings = Seq(
// coverage options
coverageMinimum := 85,
coverageFailOnMinimum := true,
coverageHighlighting := true,
coverageHighlighting := true
)
lazy val testSettings = Seq(
@ -206,9 +205,11 @@ lazy val portalSettings = Seq(
routesGenerator := InjectedRoutesGenerator,
coverageExcludedPackages := "<empty>;views.html.*;router.*;controllers\\.javascript.*;.*Reverse.*",
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,
// 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
scriptClasspath in bashScriptDefines ~= (cp => cp :+ "lib_extra/*"),
mainClass in reStart := None,
@ -241,7 +242,7 @@ lazy val portal = (project in file("modules/portal"))
.settings(testSettings)
.settings(portalSettings)
.settings(
name := "portal",
name := "portal"
)
.dependsOn(mysql)

View File

@ -17,8 +17,8 @@
package vinyldns.api.domain.membership
import java.util.UUID
import org.joda.time.DateTime
import vinyldns.core.domain.auth.AuthPrincipal
import vinyldns.core.domain.membership.GroupChangeType.GroupChangeType
import vinyldns.core.domain.membership.GroupStatus.GroupStatus
import vinyldns.core.domain.membership.LockStatus.LockStatus
@ -36,15 +36,20 @@ final case class GroupInfo(
admins: Set[UserId] = Set.empty
)
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,
name = group.name,
email = group.email,
description = group.description,
created = group.created,
status = group.status,
members = group.memberIds.map(UserId),
admins = group.adminUserIds.map(UserId)
created = if (abridged) null else group.created,
status = if (abridged) null else group.status,
members = (if (abridged && authPrincipal.isDefined) group.memberIds.filter(x => authPrincipal.get.userId == x && authPrincipal.get.isGroupMember(group.id))
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],
maxItems: Int,
authPrincipal: AuthPrincipal,
ignoreAccess: Boolean
ignoreAccess: Boolean,
abridged: Boolean = false
): Result[ListMyGroupsResponse] = {
val groupsCall =
if (authPrincipal.isSystemAdmin || ignoreAccess) {
@ -198,7 +199,7 @@ class MembershipService(
}
groupsCall.map { grp =>
pageListGroupsResponse(grp.toList, groupNameFilter, startFrom, maxItems, ignoreAccess)
pageListGroupsResponse(grp.toList, groupNameFilter, startFrom, maxItems, ignoreAccess, abridged, authPrincipal)
}
}.toResult
@ -207,12 +208,14 @@ class MembershipService(
groupNameFilter: Option[String],
startFrom: Option[String],
maxItems: Int,
ignoreAccess: Boolean
ignoreAccess: Boolean,
abridged: Boolean = false,
authPrincipal: AuthPrincipal
): ListMyGroupsResponse = {
val allMyGroups = allGroups
.filter(_.status == GroupStatus.Active)
.sortBy(_.id)
.map(GroupInfo.apply)
.map(x => GroupInfo.fromGroup(x, abridged, Some(authPrincipal)))
val filtered = allMyGroups
.filter(grp => groupNameFilter.forall(grp.name.contains(_)))

View File

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

View File

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

View File

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

View File

@ -352,7 +352,7 @@
<!-- START ACL MODAL -->
<form name="addAclRuleForm" role="form" class="form-horizontal" novalidate>
<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">
<select name="priority"
id="acl-rule-priority"
@ -373,10 +373,9 @@
<select name="groupId"
class="form-control"
ng-model="currentAclRule.groupId"
ng-controller="ZonesController"
ng-disabled="aclModal.details.readOnly"
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>
<option value="">--- Please select a group ---</option>
</select>

View File

@ -121,11 +121,12 @@ angular.module('controller.groups', []).controller('GroupsController', function
$scope.groups.items = result.groups;
$scope.groupsLoaded = true;
if (!$scope.query.length) {
$scope.hasGroups = response.data.groups.length > 0;
$scope.hasGroups = $scope.groups.items.length > 0;
}
return result;
}
getGroups($scope.ignoreAccess)
getGroupsAbridged($scope.ignoreAccess)
.then(success)
.catch(function (error) {
handleError(error, 'getGroups::refresh-failure');
@ -151,6 +152,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
$log.log('groupsService::getGroups-success');
return response.data;
}
return groupsService
.getGroups($scope.ignoreAccess, $scope.query)
.then(success)
@ -159,12 +161,25 @@ angular.module('controller.groups', []).controller('GroupsController', function
});
}
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
$scope.haveNoGroups = function (groupLength) {
if (!$scope.hasGroups && !groupLength && $scope.groupsLoaded && $scope.query.length == "") {
return true
}
else{
} else {
return false
}
}
@ -173,8 +188,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
$scope.searchCriteria = function (groupLength) {
if ($scope.groupsLoaded && !groupLength && $scope.query.length != "") {
return true
}
else{
} else {
return false
}
}
@ -240,6 +254,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
var alert = utilityService.success('Removed Group: ' + $scope.currentGroup.name, response, 'groupsService::deleteGroup successful');
$scope.alerts.push(alert);
}
groupsService.deleteGroups($scope.currentGroup.id)
.then(success)
.catch(function (error) {

View File

@ -53,15 +53,7 @@ angular.module('controller.zones', [])
$scope.currentZone.transferConnection = {};
};
groupsService.getGroups().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) {
groupsService.getGroupsAbridged(true, "").then(function (results) {
if (results.data) {
$scope.allGroups = results.data.groups;
}

View File

@ -84,6 +84,21 @@ angular.module('service.groups', [])
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) {
var url = '/api/groups/' + groupId + '/changes';
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
}
}