mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-31 14:25:30 +00:00
Merge pull request #1124 from Aravindh-Raju/aravindhr/filter-zones
Add ability to search/filter zones
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package vinyldns.api.domain.zone
|
||||
|
||||
import cats.effect.IO
|
||||
import cats.implicits._
|
||||
import vinyldns.api.domain.access.AccessValidationsAlgebra
|
||||
import vinyldns.api.Interfaces
|
||||
@@ -142,33 +143,59 @@ class ZoneService(
|
||||
accessLevel = getZoneAccess(auth, zone)
|
||||
} yield ZoneInfo(zone, aclInfo, groupName, accessLevel)
|
||||
|
||||
// List zones. Uses zone name as default while using search to list zones or by admin group name if selected.
|
||||
def listZones(
|
||||
authPrincipal: AuthPrincipal,
|
||||
nameFilter: Option[String] = None,
|
||||
startFrom: Option[String] = None,
|
||||
maxItems: Int = 100,
|
||||
searchByAdminGroup: Boolean = false,
|
||||
ignoreAccess: Boolean = false
|
||||
): Result[ListZonesResponse] = {
|
||||
for {
|
||||
listZonesResult <- zoneRepository.listZones(
|
||||
authPrincipal,
|
||||
nameFilter,
|
||||
startFrom,
|
||||
maxItems,
|
||||
ignoreAccess
|
||||
if(!searchByAdminGroup || nameFilter.isEmpty){
|
||||
for {
|
||||
listZonesResult <- zoneRepository.listZones(
|
||||
authPrincipal,
|
||||
nameFilter,
|
||||
startFrom,
|
||||
maxItems,
|
||||
ignoreAccess
|
||||
)
|
||||
zones = listZonesResult.zones
|
||||
groupIds = zones.map(_.adminGroupId).toSet
|
||||
groups <- groupRepository.getGroups(groupIds)
|
||||
zoneSummaryInfos = zoneSummaryInfoMapping(zones, authPrincipal, groups)
|
||||
} yield ListZonesResponse(
|
||||
zoneSummaryInfos,
|
||||
listZonesResult.zonesFilter,
|
||||
listZonesResult.startFrom,
|
||||
listZonesResult.nextId,
|
||||
listZonesResult.maxItems,
|
||||
listZonesResult.ignoreAccess
|
||||
)
|
||||
zones = listZonesResult.zones
|
||||
groupIds = zones.map(_.adminGroupId).toSet
|
||||
groups <- groupRepository.getGroups(groupIds)
|
||||
zoneSummaryInfos = zoneSummaryInfoMapping(zones, authPrincipal, groups)
|
||||
} yield ListZonesResponse(
|
||||
zoneSummaryInfos,
|
||||
listZonesResult.zonesFilter,
|
||||
listZonesResult.startFrom,
|
||||
listZonesResult.nextId,
|
||||
listZonesResult.maxItems,
|
||||
listZonesResult.ignoreAccess
|
||||
)
|
||||
}
|
||||
else {
|
||||
for {
|
||||
groupIds <- getGroupsIdsByName(nameFilter.get)
|
||||
listZonesResult <- zoneRepository.listZonesByAdminGroupIds(
|
||||
authPrincipal,
|
||||
startFrom,
|
||||
maxItems,
|
||||
groupIds,
|
||||
ignoreAccess
|
||||
)
|
||||
zones = listZonesResult.zones
|
||||
groups <- groupRepository.getGroups(groupIds)
|
||||
zoneSummaryInfos = zoneSummaryInfoMapping(zones, authPrincipal, groups)
|
||||
} yield ListZonesResponse(
|
||||
zoneSummaryInfos,
|
||||
nameFilter,
|
||||
listZonesResult.startFrom,
|
||||
listZonesResult.nextId,
|
||||
listZonesResult.maxItems,
|
||||
listZonesResult.ignoreAccess
|
||||
)
|
||||
}
|
||||
}.toResult
|
||||
|
||||
def zoneSummaryInfoMapping(
|
||||
@@ -242,6 +269,10 @@ class ZoneService(
|
||||
} yield zoneChange
|
||||
}
|
||||
|
||||
def getGroupsIdsByName(groupName: String): IO[Set[String]] = {
|
||||
groupRepository.getGroupsByName(groupName).map(x => x.map(_.id))
|
||||
}
|
||||
|
||||
def getBackendIds(): Result[List[String]] =
|
||||
backendResolver.ids.toList.toResult
|
||||
|
||||
|
@@ -42,6 +42,7 @@ trait ZoneServiceAlgebra {
|
||||
nameFilter: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
searchByAdminGroup: Boolean,
|
||||
ignoreAccess: Boolean
|
||||
): Result[ListZonesResponse]
|
||||
|
||||
|
@@ -78,12 +78,14 @@ class ZoneRoute(
|
||||
"nameFilter".?,
|
||||
"startFrom".as[String].?,
|
||||
"maxItems".as[Int].?(DEFAULT_MAX_ITEMS),
|
||||
"searchByAdminGroup".as[Boolean].?(false),
|
||||
"ignoreAccess".as[Boolean].?(false)
|
||||
) {
|
||||
(
|
||||
nameFilter: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
searchByAdminGroup: Boolean,
|
||||
ignoreAccess: Boolean
|
||||
) =>
|
||||
{
|
||||
@@ -94,7 +96,7 @@ class ZoneRoute(
|
||||
) {
|
||||
authenticateAndExecute(
|
||||
zoneService
|
||||
.listZones(_, nameFilter, startFrom, maxItems, ignoreAccess)
|
||||
.listZones(_, nameFilter, startFrom, maxItems, searchByAdminGroup, ignoreAccess)
|
||||
) { result =>
|
||||
complete(StatusCodes.OK, result)
|
||||
}
|
||||
|
@@ -23,6 +23,44 @@ def test_list_zones_success(list_zone_context, shared_zone_test_context):
|
||||
assert_that(result["nameFilter"], is_(f"*{shared_zone_test_context.partition_id}"))
|
||||
|
||||
|
||||
def test_list_zones_by_admin_group_name(list_zone_context, shared_zone_test_context):
|
||||
"""
|
||||
Test that we can retrieve list of zones by searching with admin group name
|
||||
"""
|
||||
result = shared_zone_test_context.list_zones_client.list_zones(name_filter=f"list-zones-group{shared_zone_test_context.partition_id}", search_by_admin_group=True, status=200)
|
||||
retrieved = result["zones"]
|
||||
|
||||
assert_that(retrieved, has_length(5))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.search_zone1["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.search_zone2["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.search_zone3["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.non_search_zone1["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.non_search_zone2["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("adminGroupName", list_zone_context.list_zones_group["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("backendId", "func-test-backend")))
|
||||
|
||||
assert_that(result["nameFilter"], is_(f"list-zones-group{shared_zone_test_context.partition_id}"))
|
||||
|
||||
|
||||
def test_list_zones_by_admin_group_name_with_wildcard(list_zone_context, shared_zone_test_context):
|
||||
"""
|
||||
Test that we can retrieve list of zones by searching with admin group name with wildcard character
|
||||
"""
|
||||
result = shared_zone_test_context.list_zones_client.list_zones(name_filter=f"*group{shared_zone_test_context.partition_id}", search_by_admin_group=True, status=200)
|
||||
retrieved = result["zones"]
|
||||
|
||||
assert_that(retrieved, has_length(5))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.search_zone1["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.search_zone2["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.search_zone3["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.non_search_zone1["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("name", list_zone_context.non_search_zone2["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("adminGroupName", list_zone_context.list_zones_group["name"])))
|
||||
assert_that(retrieved, has_item(has_entry("backendId", "func-test-backend")))
|
||||
|
||||
assert_that(result["nameFilter"], is_(f"*group{shared_zone_test_context.partition_id}"))
|
||||
|
||||
|
||||
def test_list_zones_max_items_100(shared_zone_test_context):
|
||||
"""
|
||||
Test that the default max items for a list zones request is 100
|
||||
|
@@ -445,7 +445,7 @@ class VinylDNSClient(object):
|
||||
response, data = self.make_request(url, "GET", self.headers, not_found_ok=True, **kwargs)
|
||||
return data
|
||||
|
||||
def list_zones(self, name_filter=None, start_from=None, max_items=None, ignore_access=False, **kwargs):
|
||||
def list_zones(self, name_filter=None, start_from=None, max_items=None, search_by_admin_group=False, ignore_access=False, **kwargs):
|
||||
"""
|
||||
Gets a list of zones that currently exist
|
||||
:return: a list of zones
|
||||
@@ -462,6 +462,9 @@ class VinylDNSClient(object):
|
||||
if max_items:
|
||||
query.append("maxItems=" + str(max_items))
|
||||
|
||||
if search_by_admin_group:
|
||||
query.append("searchByAdminGroup=" + str(search_by_admin_group))
|
||||
|
||||
if ignore_access:
|
||||
query.append("ignoreAccess=" + str(ignore_access))
|
||||
|
||||
|
@@ -562,6 +562,45 @@ class ZoneServiceSpec
|
||||
result.ignoreAccess shouldBe true
|
||||
}
|
||||
|
||||
"name filter must be used to return zones by admin group name, when search by admin group option is true" in {
|
||||
doReturn(IO.pure(Set(abcGroup)))
|
||||
.when(mockGroupRepo)
|
||||
.getGroupsByName(any[String])
|
||||
doReturn(IO.pure(ListZonesResults(List(abcZone), ignoreAccess = true, zonesFilter = Some("abcGroup"))))
|
||||
.when(mockZoneRepo)
|
||||
.listZonesByAdminGroupIds(abcAuth, None, 100, Set(abcGroup.id), ignoreAccess = true)
|
||||
doReturn(IO.pure(Set(abcGroup))).when(mockGroupRepo).getGroups(any[Set[String]])
|
||||
|
||||
// When searchByAdminGroup is true, zones are filtered by admin group name given in nameFilter
|
||||
val result: ListZonesResponse =
|
||||
rightResultOf(underTest.listZones(abcAuth, Some("abcGroup"), None, 100, searchByAdminGroup = true, ignoreAccess = true).value)
|
||||
result.zones shouldBe List(abcZoneSummary)
|
||||
result.maxItems shouldBe 100
|
||||
result.startFrom shouldBe None
|
||||
result.nameFilter shouldBe Some("abcGroup")
|
||||
result.nextId shouldBe None
|
||||
result.ignoreAccess shouldBe true
|
||||
}
|
||||
|
||||
"name filter must be used to return zone by zone name, when search by admin group option is false" in {
|
||||
doReturn(IO.pure(Set(abcGroup)))
|
||||
.when(mockGroupRepo)
|
||||
.getGroups(any[Set[String]])
|
||||
doReturn(IO.pure(ListZonesResults(List(abcZone), ignoreAccess = true, zonesFilter = Some("abcZone"))))
|
||||
.when(mockZoneRepo)
|
||||
.listZones(abcAuth, Some("abcZone"), None, 100, true)
|
||||
|
||||
// When searchByAdminGroup is false, zone name given in nameFilter is returned
|
||||
val result: ListZonesResponse =
|
||||
rightResultOf(underTest.listZones(abcAuth, Some("abcZone"), None, 100, searchByAdminGroup = false, ignoreAccess = true).value)
|
||||
result.zones shouldBe List(abcZoneSummary)
|
||||
result.maxItems shouldBe 100
|
||||
result.startFrom shouldBe None
|
||||
result.nameFilter shouldBe Some("abcZone")
|
||||
result.nextId shouldBe None
|
||||
result.ignoreAccess shouldBe true
|
||||
}
|
||||
|
||||
"return Unknown group name if zone admin group cannot be found" in {
|
||||
doReturn(IO.pure(ListZonesResults(List(abcZone, xyzZone))))
|
||||
.when(mockZoneRepo)
|
||||
|
@@ -84,6 +84,14 @@ trait EmptyZoneRepo extends ZoneRepository {
|
||||
|
||||
def getZoneByName(zoneName: String): IO[Option[Zone]] = IO.pure(None)
|
||||
|
||||
def listZonesByAdminGroupIds(
|
||||
authPrincipal: AuthPrincipal,
|
||||
startFrom: Option[String] = None,
|
||||
maxItems: Int = 100,
|
||||
adminGroupIds: Set[String],
|
||||
ignoreAccess: Boolean = false
|
||||
): IO[ListZonesResults] = IO.pure(ListZonesResults())
|
||||
|
||||
def listZones(
|
||||
authPrincipal: AuthPrincipal,
|
||||
zoneNameFilter: Option[String] = None,
|
||||
@@ -113,6 +121,8 @@ trait EmptyGroupRepo extends GroupRepository {
|
||||
|
||||
def getGroupByName(groupName: String): IO[Option[Group]] = IO.pure(None)
|
||||
|
||||
def getGroupsByName(groupName: String): IO[Set[Group]] = IO.pure(Set())
|
||||
|
||||
def getAllGroups(): IO[Set[Group]] = IO.pure(Set())
|
||||
}
|
||||
|
||||
|
@@ -252,6 +252,7 @@ class ZoneRoutingSpec
|
||||
nameFilter: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
searchByAdminGroup: Boolean = false,
|
||||
ignoreAccess: Boolean = false
|
||||
): Result[ListZonesResponse] = {
|
||||
|
||||
@@ -920,6 +921,20 @@ class ZoneRoutingSpec
|
||||
}
|
||||
}
|
||||
|
||||
"return zones by admin group name when searchByAdminGroup is true" in {
|
||||
Get(s"/zones?nameFilter=ok&startFrom=zone4.&maxItems=4&searchByAdminGroup=true") ~> zoneRoute ~> check {
|
||||
val resp = responseAs[ListZonesResponse]
|
||||
val zones = resp.zones
|
||||
(zones.map(_.id) should contain)
|
||||
.only(zone1.id, zone2.id, zone3.id)
|
||||
resp.nextId shouldBe None
|
||||
resp.maxItems shouldBe 4
|
||||
resp.startFrom shouldBe Some("zone4.")
|
||||
resp.nameFilter shouldBe Some("ok")
|
||||
resp.ignoreAccess shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
"return all zones when list all is true" in {
|
||||
Get(s"/zones?maxItems=5&ignoreAccess=true") ~> zoneRoute ~> check {
|
||||
val resp = responseAs[ListZonesResponse]
|
||||
|
@@ -33,6 +33,8 @@ trait GroupRepository extends Repository {
|
||||
|
||||
def getGroupByName(groupName: String): IO[Option[Group]]
|
||||
|
||||
def getGroupsByName(groupName: String): IO[Set[Group]]
|
||||
|
||||
def getAllGroups(): IO[Set[Group]]
|
||||
|
||||
}
|
||||
|
@@ -35,6 +35,14 @@ trait ZoneRepository extends Repository {
|
||||
|
||||
def getZonesByFilters(zoneNames: Set[String]): IO[Set[Zone]]
|
||||
|
||||
def listZonesByAdminGroupIds(
|
||||
authPrincipal: AuthPrincipal,
|
||||
startFrom: Option[String] = None,
|
||||
maxItems: Int = 100,
|
||||
adminGroupIds: Set[String],
|
||||
ignoreAccess: Boolean = false
|
||||
): IO[ListZonesResults]
|
||||
|
||||
def listZones(
|
||||
authPrincipal: AuthPrincipal,
|
||||
zoneNameFilter: Option[String] = None,
|
||||
|
@@ -103,6 +103,20 @@ class MySqlGroupRepositoryIntegrationSpec
|
||||
}
|
||||
}
|
||||
|
||||
"MySqlGroupRepository.getGroupsByName" should {
|
||||
"retrieve a group" in {
|
||||
repo.getGroupsByName(groups.head.name).unsafeRunSync() shouldBe Set(groups.head)
|
||||
}
|
||||
|
||||
"retrieve groups with wildcard character" in {
|
||||
repo.getGroupsByName("*-group-*").unsafeRunSync() shouldBe groups.toSet
|
||||
}
|
||||
|
||||
"returns empty set when group does not exist" in {
|
||||
repo.getGroupsByName("no-existo").unsafeRunSync() shouldBe Set()
|
||||
}
|
||||
}
|
||||
|
||||
"MySqlGroupRepository.getAllGroups" should {
|
||||
"retrieve all groups" in {
|
||||
repo.getAllGroups().unsafeRunSync() should contain theSameElementsAs groups.toSet
|
||||
|
@@ -29,14 +29,16 @@ import vinyldns.core.domain.zone._
|
||||
import vinyldns.core.TestZoneData.okZone
|
||||
import vinyldns.core.TestMembershipData._
|
||||
import vinyldns.core.domain.zone.ZoneRepository.DuplicateZoneError
|
||||
import vinyldns.mysql.TestMySqlInstance
|
||||
import vinyldns.mysql.{TestMySqlInstance, TransactionProvider}
|
||||
import vinyldns.mysql.TestMySqlInstance.groupRepository
|
||||
|
||||
class MySqlZoneRepositoryIntegrationSpec
|
||||
extends AnyWordSpec
|
||||
with BeforeAndAfterAll
|
||||
with BeforeAndAfterEach
|
||||
with Matchers
|
||||
with Inspectors {
|
||||
with Inspectors
|
||||
with TransactionProvider {
|
||||
|
||||
private var repo: ZoneRepository = _
|
||||
|
||||
@@ -221,6 +223,32 @@ class MySqlZoneRepositoryIntegrationSpec
|
||||
(repo.listZones(dummyAuth).unsafeRunSync().zones should contain).only(testZones.head)
|
||||
}
|
||||
|
||||
"get authorized zone by admin group name" in {
|
||||
|
||||
executeWithinTransaction { db: DB =>
|
||||
groupRepository.save(db, okGroup.copy(id = testZoneAdminGroupId))
|
||||
}.unsafeRunSync()
|
||||
|
||||
// store all of the zones
|
||||
|
||||
val f = saveZones(testZones)
|
||||
|
||||
// query for all zones for the ok user, he should have access to all of the zones
|
||||
val okUserAuth = AuthPrincipal(
|
||||
signedInUser = okUser,
|
||||
memberGroupIds = groups.map(_.id)
|
||||
)
|
||||
|
||||
f.unsafeRunSync()
|
||||
repo.listZonesByAdminGroupIds(okUserAuth, None, 100, Set(testZoneAdminGroupId)).unsafeRunSync().zones should contain theSameElementsAs testZones
|
||||
|
||||
// dummy user only has access to one zone
|
||||
(repo.listZonesByAdminGroupIds(dummyAuth, None, 100, Set(testZoneAdminGroupId)).unsafeRunSync().zones should contain).only(testZones.head)
|
||||
|
||||
// delete the group created to test
|
||||
groupRepository.delete(okGroup).unsafeRunSync()
|
||||
}
|
||||
|
||||
"get all zones" in {
|
||||
// store all of the zones
|
||||
val privateZone = okZone.copy(
|
||||
@@ -259,6 +287,82 @@ class MySqlZoneRepositoryIntegrationSpec
|
||||
.zones should contain theSameElementsAs testZones
|
||||
}
|
||||
|
||||
"get all zones by admin group name" in {
|
||||
|
||||
executeWithinTransaction { db: DB =>
|
||||
groupRepository.save(db, okGroup)
|
||||
}.unsafeRunSync()
|
||||
|
||||
val group = groupRepository.getGroupsByName(okGroup.name).unsafeRunSync()
|
||||
val groupId = group.head.id
|
||||
|
||||
// store all of the zones
|
||||
val privateZone = okZone.copy(
|
||||
name = "private-zone.",
|
||||
id = UUID.randomUUID().toString,
|
||||
acl = ZoneACL(),
|
||||
adminGroupId = groupId
|
||||
)
|
||||
|
||||
val sharedZone = okZone.copy(
|
||||
name = "shared-zone.",
|
||||
id = UUID.randomUUID().toString,
|
||||
acl = ZoneACL(),
|
||||
shared = true,
|
||||
adminGroupId = groupId
|
||||
)
|
||||
|
||||
val testZones = Seq(privateZone, sharedZone)
|
||||
|
||||
val f = saveZones(testZones)
|
||||
|
||||
// query for all zones for the ok user, should have all of the zones returned
|
||||
val okUserAuth = AuthPrincipal(
|
||||
signedInUser = okUser,
|
||||
memberGroupIds = groups.map(_.id)
|
||||
)
|
||||
|
||||
f.unsafeRunSync()
|
||||
|
||||
repo
|
||||
.listZonesByAdminGroupIds(okUserAuth, None, 100, Set(groupId), ignoreAccess = true)
|
||||
.unsafeRunSync()
|
||||
.zones should contain theSameElementsAs testZones
|
||||
|
||||
// dummy user only have all of the zones returned
|
||||
repo
|
||||
.listZonesByAdminGroupIds(dummyAuth, None, 100, Set(groupId), ignoreAccess = true)
|
||||
.unsafeRunSync()
|
||||
.zones should contain theSameElementsAs testZones
|
||||
|
||||
|
||||
// delete the group created to test
|
||||
groupRepository.delete(okGroup).unsafeRunSync()
|
||||
}
|
||||
|
||||
"get empty list when no matching admin group name is found while filtering zones by group name" in {
|
||||
|
||||
executeWithinTransaction { db: DB =>
|
||||
groupRepository.save(db, okGroup.copy(id = testZoneAdminGroupId))
|
||||
}.unsafeRunSync()
|
||||
|
||||
// store all of the zones
|
||||
|
||||
val f = saveZones(testZones)
|
||||
|
||||
// query for all zones for the ok user, he should have access to all of the zones
|
||||
val okUserAuth = AuthPrincipal(
|
||||
signedInUser = okUser,
|
||||
memberGroupIds = groups.map(_.id)
|
||||
)
|
||||
|
||||
f.unsafeRunSync()
|
||||
repo.listZonesByAdminGroupIds(okUserAuth, None, 100, Set()).unsafeRunSync().zones shouldBe empty
|
||||
|
||||
// delete the group created to test
|
||||
groupRepository.delete(okGroup).unsafeRunSync()
|
||||
}
|
||||
|
||||
"get zones that are accessible by everyone" in {
|
||||
|
||||
//user and group id being set to None implies EVERYONE access
|
||||
|
@@ -155,6 +155,30 @@ class MySqlGroupRepository extends GroupRepository with GroupProtobufConversions
|
||||
}
|
||||
}
|
||||
|
||||
def getGroupsByName(nameFilter: String): IO[Set[Group]] =
|
||||
monitor("repo.Group.getGroupByName") {
|
||||
IO {
|
||||
logger.debug(s"Getting groups with name: $nameFilter")
|
||||
val initialQuery = "SELECT data FROM groups WHERE name"
|
||||
val sb = new StringBuilder
|
||||
sb.append(initialQuery)
|
||||
val groupsLike = if (nameFilter.contains('*')) {
|
||||
s" LIKE '${nameFilter.replace('*', '%')}'"
|
||||
} else {
|
||||
s" LIKE '$nameFilter%'"
|
||||
}
|
||||
sb.append(groupsLike)
|
||||
val query = sb.toString()
|
||||
|
||||
DB.readOnly { implicit s =>
|
||||
SQL(query)
|
||||
.map(toGroup(1))
|
||||
.list()
|
||||
.apply()
|
||||
}.toSet
|
||||
}
|
||||
}
|
||||
|
||||
def getAllGroups(): IO[Set[Group]] =
|
||||
monitor("repo.Group.getAllGroups") {
|
||||
IO {
|
||||
|
@@ -229,6 +229,65 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is somewhat complicated due to how we need to build the SQL.
|
||||
*
|
||||
* - Dynamically build the accessor list combining the user id and group ids
|
||||
* - Dynamically build the LIMIT clause. We cannot specify an offset if this is the first page (offset == 0)
|
||||
*
|
||||
* @return a ListZonesResults
|
||||
*/
|
||||
def listZonesByAdminGroupIds(
|
||||
authPrincipal: AuthPrincipal,
|
||||
startFrom: Option[String] = None,
|
||||
maxItems: Int = 100,
|
||||
adminGroupIds: Set[String],
|
||||
ignoreAccess: Boolean = false
|
||||
): IO[ListZonesResults] =
|
||||
monitor("repo.ZoneJDBC.listZonesByAdminGroupIds") {
|
||||
IO {
|
||||
DB.readOnly { implicit s =>
|
||||
val (withAccessorCheck, accessors) =
|
||||
withAccessors(authPrincipal.signedInUser, authPrincipal.memberGroupIds, ignoreAccess)
|
||||
val sb = new StringBuilder
|
||||
sb.append(withAccessorCheck)
|
||||
|
||||
if(adminGroupIds.nonEmpty) {
|
||||
val groupIds = adminGroupIds.map(x => "'" + x + "'").mkString(",")
|
||||
sb.append(s" WHERE admin_group_id IN ($groupIds) ")
|
||||
} else {
|
||||
sb.append(s" WHERE admin_group_id IN ('') ")
|
||||
}
|
||||
|
||||
sb.append(s" GROUP BY z.name ")
|
||||
sb.append(s" LIMIT ${maxItems + 1}")
|
||||
|
||||
val query = sb.toString
|
||||
|
||||
val results: List[Zone] = SQL(query)
|
||||
.bind(accessors: _*)
|
||||
.map(extractZone(1))
|
||||
.list()
|
||||
.apply()
|
||||
|
||||
val (newResults, nextId) =
|
||||
if (results.size > maxItems)
|
||||
(results.dropRight(1), results.dropRight(1).lastOption.map(_.name))
|
||||
else (results, None)
|
||||
|
||||
|
||||
ListZonesResults(
|
||||
zones = newResults,
|
||||
nextId = nextId,
|
||||
startFrom = startFrom,
|
||||
maxItems = maxItems,
|
||||
zonesFilter = None,
|
||||
ignoreAccess = ignoreAccess,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is somewhat complicated due to how we need to build the SQL.
|
||||
*
|
||||
|
@@ -59,7 +59,12 @@
|
||||
<span class="fa fa-search"></span>
|
||||
</button>
|
||||
</span>
|
||||
<input id="zone-search-text" ng-model="query" type="text" class="form-control" placeholder="Zone Name"/>
|
||||
<input id="zone-search-text" ng-model="query" type="text" class="form-control" placeholder="{{!searchByAdminGroup ? 'Zone Name' : 'Admin Group Name'}}"/>
|
||||
</div>
|
||||
<div class="checkbox pull-right">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="searchByAdminGroup" ng-change="refreshZones()"> Search by admin group
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -168,8 +173,14 @@
|
||||
<span class="fa fa-search"></span>
|
||||
</button>
|
||||
</span>
|
||||
<input id="all-zones-search-text" ng-model="query" type="text" class="form-control" placeholder="Zone Name"/>
|
||||
<input id="all-zones-search-text" ng-model="query" type="text" class="form-control" placeholder="{{!searchByAdminGroup ? 'Zone Name' : 'Admin Group Name'}}"/>
|
||||
</div>
|
||||
|
||||
<div class="checkbox pull-right">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="searchByAdminGroup" ng-change="refreshZones()"> Search by admin group
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- END SEARCH BOX -->
|
||||
|
@@ -87,7 +87,7 @@ angular.module('controller.zones', [])
|
||||
allZonesPaging = pagingService.resetPaging(allZonesPaging);
|
||||
|
||||
zonesService
|
||||
.getZones(zonesPaging.maxItems, undefined, $scope.query)
|
||||
.getZones(zonesPaging.maxItems, undefined, $scope.query, $scope.searchByAdminGroup)
|
||||
.then(function (response) {
|
||||
$log.log('zonesService::getZones-success (' + response.data.zones.length + ' zones)');
|
||||
zonesPaging.next = response.data.nextId;
|
||||
@@ -101,7 +101,7 @@ angular.module('controller.zones', [])
|
||||
});
|
||||
|
||||
zonesService
|
||||
.getZones(zonesPaging.maxItems, undefined, $scope.query, true)
|
||||
.getZones(zonesPaging.maxItems, undefined, $scope.query, $scope.searchByAdminGroup, true)
|
||||
.then(function (response) {
|
||||
$log.log('zonesService::getZones-success (' + response.data.zones.length + ' zones)');
|
||||
allZonesPaging.next = response.data.nextId;
|
||||
@@ -207,7 +207,7 @@ angular.module('controller.zones', [])
|
||||
$scope.prevPageMyZones = function() {
|
||||
var startFrom = pagingService.getPrevStartFrom(zonesPaging);
|
||||
return zonesService
|
||||
.getZones(zonesPaging.maxItems, startFrom, $scope.query, false)
|
||||
.getZones(zonesPaging.maxItems, startFrom, $scope.query, $scope.searchByAdminGroup, false)
|
||||
.then(function(response) {
|
||||
zonesPaging = pagingService.prevPageUpdate(response.data.nextId, zonesPaging);
|
||||
updateZoneDisplay(response.data.zones);
|
||||
@@ -220,7 +220,7 @@ angular.module('controller.zones', [])
|
||||
$scope.prevPageAllZones = function() {
|
||||
var startFrom = pagingService.getPrevStartFrom(allZonesPaging);
|
||||
return zonesService
|
||||
.getZones(allZonesPaging.maxItems, startFrom, $scope.query, true)
|
||||
.getZones(allZonesPaging.maxItems, startFrom, $scope.query, $scope.searchByAdminGroup, true)
|
||||
.then(function(response) {
|
||||
allZonesPaging = pagingService.prevPageUpdate(response.data.nextId, allZonesPaging);
|
||||
updateAllZonesDisplay(response.data.zones);
|
||||
@@ -232,7 +232,7 @@ angular.module('controller.zones', [])
|
||||
|
||||
$scope.nextPageMyZones = function () {
|
||||
return zonesService
|
||||
.getZones(zonesPaging.maxItems, zonesPaging.next, $scope.query, false)
|
||||
.getZones(zonesPaging.maxItems, zonesPaging.next, $scope.query, $scope.searchByAdminGroup, false)
|
||||
.then(function(response) {
|
||||
var zoneSets = response.data.zones;
|
||||
zonesPaging = pagingService.nextPageUpdate(zoneSets, response.data.nextId, zonesPaging);
|
||||
@@ -248,7 +248,7 @@ angular.module('controller.zones', [])
|
||||
|
||||
$scope.nextPageAllZones = function () {
|
||||
return zonesService
|
||||
.getZones(allZonesPaging.maxItems, allZonesPaging.next, $scope.query, true)
|
||||
.getZones(allZonesPaging.maxItems, allZonesPaging.next, $scope.query, $scope.searchByAdminGroup, true)
|
||||
.then(function(response) {
|
||||
var zoneSets = response.data.zones;
|
||||
allZonesPaging = pagingService.nextPageUpdate(zoneSets, response.data.nextId, allZonesPaging);
|
||||
|
@@ -76,13 +76,14 @@ describe('Controller: ZonesController', function () {
|
||||
var expectedMaxItems = 100;
|
||||
var expectedStartFrom = undefined;
|
||||
var expectedQuery = this.scope.query;
|
||||
var expectedSearchByAdminGroup = this.scope.searchByAdminGroup;
|
||||
var expectedignoreAccess = false;
|
||||
|
||||
this.scope.nextPageMyZones();
|
||||
|
||||
expect(getZoneSets.calls.count()).toBe(1);
|
||||
expect(getZoneSets.calls.mostRecent().args).toEqual(
|
||||
[expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess]);
|
||||
[expectedMaxItems, expectedStartFrom, expectedQuery, expectedSearchByAdminGroup, expectedignoreAccess]);
|
||||
});
|
||||
|
||||
it('prevPageMyZones should call getZones with the correct parameters', function () {
|
||||
@@ -93,19 +94,20 @@ describe('Controller: ZonesController', function () {
|
||||
var expectedMaxItems = 100;
|
||||
var expectedStartFrom = undefined;
|
||||
var expectedQuery = this.scope.query;
|
||||
var expectedSearchByAdminGroup = this.scope.searchByAdminGroup;
|
||||
var expectedignoreAccess = false;
|
||||
|
||||
this.scope.prevPageMyZones();
|
||||
|
||||
expect(getZoneSets.calls.count()).toBe(1);
|
||||
expect(getZoneSets.calls.mostRecent().args).toEqual(
|
||||
[expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess]);
|
||||
[expectedMaxItems, expectedStartFrom, expectedQuery, expectedSearchByAdminGroup, expectedignoreAccess]);
|
||||
|
||||
this.scope.nextPageMyZones();
|
||||
this.scope.prevPageMyZones();
|
||||
|
||||
expect(getZoneSets.calls.count()).toBe(3);
|
||||
expect(getZoneSets.calls.mostRecent().args).toEqual(
|
||||
[expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess]);
|
||||
[expectedMaxItems, expectedStartFrom, expectedQuery, expectedSearchByAdminGroup, expectedignoreAccess]);
|
||||
});
|
||||
});
|
||||
|
@@ -19,7 +19,7 @@
|
||||
angular.module('service.zones', [])
|
||||
.service('zonesService', function ($http, groupsService, $log, utilityService) {
|
||||
|
||||
this.getZones = function (limit, startFrom, query, ignoreAccess) {
|
||||
this.getZones = function (limit, startFrom, query, searchByAdminGroup, ignoreAccess) {
|
||||
if (query == "") {
|
||||
query = null;
|
||||
}
|
||||
@@ -27,6 +27,7 @@ angular.module('service.zones', [])
|
||||
"maxItems": limit,
|
||||
"startFrom": startFrom,
|
||||
"nameFilter": query,
|
||||
"searchByAdminGroup": searchByAdminGroup,
|
||||
"ignoreAccess": ignoreAccess
|
||||
};
|
||||
var url = groupsService.urlBuilder("/api/zones", params);
|
||||
|
@@ -27,8 +27,8 @@ describe('Service: zoneService', function () {
|
||||
}));
|
||||
|
||||
it('http backend gets called properly when getting zones', function () {
|
||||
this.$httpBackend.expectGET('/api/zones?maxItems=100&startFrom=start&nameFilter=someQuery&ignoreAccess=false').respond('zone returned');
|
||||
this.zonesService.getZones('100', 'start', 'someQuery', false)
|
||||
this.$httpBackend.expectGET('/api/zones?maxItems=100&startFrom=start&nameFilter=someQuery&searchByAdminGroup=false&ignoreAccess=false').respond('zone returned');
|
||||
this.zonesService.getZones('100', 'start', 'someQuery', false, false)
|
||||
.then(function(response) {
|
||||
expect(response.data).toBe('zone returned');
|
||||
});
|
||||
|
Reference in New Issue
Block a user