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:
parent
d9f986997d
commit
02d702f461
13
build.sbt
13
build.sbt
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
abridged: Boolean = false,
|
||||||
|
authPrincipal: AuthPrincipal
|
||||||
): ListMyGroupsResponse = {
|
): 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(_)))
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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,6 +152,7 @@ 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)
|
||||||
@ -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
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,8 +188,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
|
|||||||
$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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,6 +254,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
|
|||||||
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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 });
|
||||||
|
14
project/PreparePortalHook.scala
Normal file
14
project/PreparePortalHook.scala
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user