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

Support record owner group filtering in record set search (#936)

* Support record owner group filter in list record set calls.
* Update unit tests.
* Update portal UI.
This commit is contained in:
Michael Ly 2020-04-13 10:31:06 -05:00 committed by GitHub
parent 1e6dad534d
commit 5d2f2b87da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 178 additions and 57 deletions

View File

@ -180,6 +180,7 @@ class RecordSetService(
maxItems: Option[Int],
recordNameFilter: String,
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort,
authPrincipal: AuthPrincipal
): Result[ListGlobalRecordSetsResponse] =
@ -193,6 +194,7 @@ class RecordSetService(
maxItems,
Some(formattedRecordNameFilter),
recordTypeFilter,
recordOwnerGroupFilter,
nameSort
)
.toResult[ListRecordSetResults]
@ -208,6 +210,7 @@ class RecordSetService(
recordSetResults.maxItems,
recordNameFilter,
recordSetResults.recordTypeFilter,
recordSetResults.recordOwnerGroupFilter,
recordSetResults.nameSort
)
@ -217,6 +220,7 @@ class RecordSetService(
maxItems: Option[Int],
recordNameFilter: Option[String],
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort,
authPrincipal: AuthPrincipal
): Result[ListRecordSetsByZoneResponse] =
@ -230,6 +234,7 @@ class RecordSetService(
maxItems,
recordNameFilter,
recordTypeFilter,
recordOwnerGroupFilter,
nameSort
)
.toResult[ListRecordSetResults]
@ -244,6 +249,7 @@ class RecordSetService(
recordSetResults.maxItems,
recordSetResults.recordNameFilter,
recordSetResults.recordTypeFilter,
recordSetResults.recordOwnerGroupFilter,
recordSetResults.nameSort
)

View File

@ -52,6 +52,7 @@ trait RecordSetServiceAlgebra {
maxItems: Option[Int],
recordNameFilter: String,
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupId: Option[String],
nameSort: NameSort,
authPrincipal: AuthPrincipal
): Result[ListGlobalRecordSetsResponse]
@ -62,6 +63,7 @@ trait RecordSetServiceAlgebra {
maxItems: Option[Int],
recordNameFilter: Option[String],
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupId: Option[String],
nameSort: NameSort,
authPrincipal: AuthPrincipal
): Result[ListRecordSetsByZoneResponse]

View File

@ -108,6 +108,7 @@ case class VinylDNSZoneViewLoader(zone: Zone, recordSetRepository: RecordSetRepo
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
.map { result =>

View File

@ -38,6 +38,7 @@ case class ListGlobalRecordSetsResponse(
maxItems: Option[Int] = None,
recordNameFilter: String,
recordTypeFilter: Option[Set[RecordType]] = None,
recordOwnerGroupFilter: Option[String] = None,
nameSort: NameSort
)
@ -48,6 +49,7 @@ case class ListRecordSetsByZoneResponse(
maxItems: Option[Int] = None,
recordNameFilter: Option[String] = None,
recordTypeFilter: Option[Set[RecordType]] = None,
recordOwnerGroupFilter: Option[String] = None,
nameSort: NameSort
)
@ -91,6 +93,7 @@ class RecordSetRoute(
"maxItems".as[Int].?(DEFAULT_MAX_ITEMS),
"recordNameFilter".?,
"recordTypeFilter".?,
"recordOwnerGroupFilter".?,
"nameSort".as[String].?("ASC")
) {
(
@ -98,6 +101,7 @@ class RecordSetRoute(
maxItems: Int,
recordNameFilter: Option[String],
recordTypeFilter: Option[String],
recordOwnerGroupFilter: Option[String],
nameSort: String
) =>
val convertedRecordTypeFilter = convertRecordTypeFilter(recordTypeFilter)
@ -114,6 +118,7 @@ class RecordSetRoute(
Some(maxItems),
recordNameFilter,
convertedRecordTypeFilter,
recordOwnerGroupFilter,
NameSort.find(nameSort),
_
)
@ -132,6 +137,7 @@ class RecordSetRoute(
"maxItems".as[Int].?(DEFAULT_MAX_ITEMS),
"recordNameFilter".as[String],
"recordTypeFilter".?,
"recordOwnerGroupFilter".?,
"nameSort".as[String].?("ASC")
) {
(
@ -139,6 +145,7 @@ class RecordSetRoute(
maxItems: Int,
recordNameFilter: String,
recordTypeFilter: Option[String],
recordOwnerGroupFilter: Option[String],
nameSort: String
) =>
val convertedRecordTypeFilter = convertRecordTypeFilter(recordTypeFilter)
@ -154,6 +161,7 @@ class RecordSetRoute(
Some(maxItems),
recordNameFilter,
convertedRecordTypeFilter,
recordOwnerGroupFilter,
NameSort.find(nameSort),
_
)

View File

@ -974,7 +974,8 @@ class RecordSetServiceSpec
ListRecordSetResults(
List(sharedZoneRecord),
recordNameFilter = Some("aaaa*"),
nameSort = NameSort.ASC
nameSort = NameSort.ASC,
recordOwnerGroupFilter = Some("owner group id")
)
)
).when(mockRecordRepo)
@ -984,6 +985,7 @@ class RecordSetServiceSpec
maxItems = any[Option[Int]],
recordNameFilter = any[Option[String]],
recordTypeFilter = any[Option[Set[RecordType.RecordType]]],
recordOwnerGroupFilter = any[Option[String]],
nameSort = any[NameSort.NameSort]
)
@ -994,6 +996,7 @@ class RecordSetServiceSpec
maxItems = None,
recordNameFilter = "aaaa*",
recordTypeFilter = None,
recordOwnerGroupFilter = Some("owner group id"),
nameSort = NameSort.ASC,
authPrincipal = sharedAuth
)
@ -1010,7 +1013,7 @@ class RecordSetServiceSpec
)
}
"fail if recordNameFilter is less than two characters" in {
"fail if recordNameFilter is fewer than two characters" in {
val result = leftResultOf(
underTest
.listRecordSets(
@ -1018,6 +1021,7 @@ class RecordSetServiceSpec
maxItems = None,
recordNameFilter = "a",
recordTypeFilter = None,
recordOwnerGroupFilter = Some("owner group id"),
nameSort = NameSort.ASC,
authPrincipal = okAuth
)
@ -1047,6 +1051,7 @@ class RecordSetServiceSpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -1059,6 +1064,7 @@ class RecordSetServiceSpec
recordNameFilter = None,
authPrincipal = sharedAuth,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
.value
@ -1088,6 +1094,7 @@ class RecordSetServiceSpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -1099,6 +1106,7 @@ class RecordSetServiceSpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC,
authPrincipal = AuthPrincipal(okAuth.signedInUser.copy(isSupport = true), Seq.empty)
)
@ -1117,6 +1125,7 @@ class RecordSetServiceSpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC,
authPrincipal = okAuth
)

View File

@ -88,7 +88,7 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
"load the DNS Zones" in {
val mockRecordSetRepo = mock[RecordSetRepository]
doReturn(IO(ListRecordSetResults(records, None, None, None, None, None, NameSort.ASC)))
doReturn(IO(ListRecordSetResults(records, None, None, None, None, None, None, NameSort.ASC)))
.when(mockRecordSetRepo)
.listRecordSets(
any[Option[String]],
@ -96,6 +96,7 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
any[Option[Int]],
any[Option[String]],
any[Option[Set[RecordType]]],
any[Option[String]],
any[NameSort]
)

View File

@ -170,7 +170,7 @@ class ZoneSyncHandlerSpec
reset(mockVinylDNSLoader)
doReturn(
IO(ListRecordSetResults(List(testRecord1), None, None, None, None, None, NameSort.ASC))
IO(ListRecordSetResults(List(testRecord1), None, None, None, None, None, None, NameSort.ASC))
).when(recordSetRepo)
.listRecordSets(
any[Option[String]],
@ -178,6 +178,7 @@ class ZoneSyncHandlerSpec
any[Option[Int]],
any[Option[String]],
any[Option[Set[RecordType]]],
any[Option[String]],
any[NameSort]
)
doReturn(IO(testChangeSet)).when(recordSetRepo).apply(any[ChangeSet])

View File

@ -52,6 +52,7 @@ trait EmptyRecordSetRepo extends RecordSetRepository {
maxItems: Option[Int],
recordNameFilter: Option[String],
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort
): IO[ListRecordSetResults] =
IO.pure(ListRecordSetResults(nameSort = nameSort))

View File

@ -181,7 +181,8 @@ class RecordSetRoutingSpec
RecordSetStatus.Active,
DateTime.now,
None,
List(AData("10.1.1.1"))
List(AData("10.1.1.1")),
ownerGroupId = Some("my-group")
)
private val rs3 = RecordSet(
@ -516,6 +517,7 @@ class RecordSetRoutingSpec
maxItems: Option[Int],
recordNameFilter: String,
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort,
authPrincipal: AuthPrincipal
): Result[ListGlobalRecordSetsResponse] = {
@ -530,22 +532,29 @@ class RecordSetRoutingSpec
maxItems,
"rs*",
recordTypeFilter,
recordOwnerGroupFilter,
nameSort
)
)
} else {
Right(
ListGlobalRecordSetsResponse(
val recordSetList = recordOwnerGroupFilter match {
case Some("my-group") => List(RecordSetGlobalInfo(rs2, okZone.name, okZone.shared, None))
case _ =>
List(
RecordSetGlobalInfo(rs1, okZone.name, okZone.shared, None),
RecordSetGlobalInfo(rs2, okZone.name, okZone.shared, None),
RecordSetGlobalInfo(rs3, okZone.name, okZone.shared, None)
),
)
}
Right(
ListGlobalRecordSetsResponse(
recordSetList,
startFrom,
None,
maxItems,
"rs*",
recordTypeFilter,
None,
nameSort
)
)
@ -558,6 +567,7 @@ class RecordSetRoutingSpec
maxItems: Option[Int],
recordNameFilter: Option[String],
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort,
authPrincipal: AuthPrincipal
): Result[ListRecordSetsByZoneResponse] = {
@ -574,6 +584,7 @@ class RecordSetRoutingSpec
maxItems,
recordNameFilter,
recordTypeFilter,
recordOwnerGroupFilter,
nameSort
)
)
@ -590,6 +601,7 @@ class RecordSetRoutingSpec
maxItems,
recordNameFilter,
recordTypeFilter,
None,
nameSort
)
)
@ -955,6 +967,15 @@ class RecordSetRoutingSpec
.only(rs1.id, rs2.id, rs3.id)
}
}
"return all recordsets of a specific owner group" in {
Get(s"/recordsets?recordNameFilter=rs*&recordOwnerGroupFilter=my-group") ~> recordSetRoute ~> check {
status shouldBe StatusCodes.OK
val resultRs = responseAs[ListGlobalRecordSetsResponse]
(resultRs.recordSets.map(_.id) should contain)
.only(rs2.id)
}
}
}
"GET recordsets by zone" should {

View File

@ -36,5 +36,6 @@ case class ListRecordSetResults(
maxItems: Option[Int] = None,
recordNameFilter: Option[String] = None,
recordTypeFilter: Option[Set[RecordType]] = None,
recordOwnerGroupFilter: Option[String] = None,
nameSort: NameSort
)

View File

@ -31,6 +31,7 @@ trait RecordSetRepository extends Repository {
maxItems: Option[Int],
recordNameFilter: Option[String],
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort
): IO[ListRecordSetResults]

View File

@ -98,6 +98,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -112,6 +113,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -197,6 +199,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(1),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -215,6 +218,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(1),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -229,6 +233,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(1),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -248,6 +253,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(1),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -261,6 +267,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(2),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -279,6 +286,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(6),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -298,6 +306,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(6),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -314,6 +323,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = Some(7),
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -335,6 +345,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = None,
recordNameFilter = Some("AAAA"),
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -353,6 +364,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = None,
recordNameFilter = Some("A"),
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -373,6 +385,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = None,
recordNameFilter = Some("Dummy"),
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)
@ -426,6 +439,7 @@ class DynamoDBRecordSetRepositoryIntegrationSpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
recordOwnerGroupFilter = None,
nameSort = NameSort.ASC
)

View File

@ -142,6 +142,7 @@ class DynamoDBRecordSetRepository private[repository] (
maxItems: Option[Int],
recordNameFilter: Option[String],
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort
): IO[ListRecordSetResults] =
monitor("repo.RecordSet.listRecordSets") {
@ -195,6 +196,7 @@ class DynamoDBRecordSetRepository private[repository] (
maxItems,
recordNameFilter,
recordTypeFilter,
recordOwnerGroupFilter,
nameSort
)
}

View File

@ -185,7 +185,8 @@ class DynamoDBRecordSetRepositorySpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
nameSort = NameSort.ASC
nameSort = NameSort.ASC,
recordOwnerGroupFilter = None
)
.unsafeRunSync()
@ -209,7 +210,15 @@ class DynamoDBRecordSetRepositorySpec
val response =
store
.listRecordSets(Some(rsOk.zoneId), None, Some(3), None, None, NameSort.ASC)
.listRecordSets(
Some(rsOk.zoneId),
None,
Some(3),
None,
None,
None,
NameSort.ASC
)
.unsafeRunSync()
verify(dynamoDBHelper).query(any[QueryRequest])
@ -227,7 +236,8 @@ class DynamoDBRecordSetRepositorySpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
nameSort = NameSort.ASC
nameSort = NameSort.ASC,
recordOwnerGroupFilter = None
)
}
@ -240,7 +250,8 @@ class DynamoDBRecordSetRepositorySpec
maxItems = None,
recordNameFilter = None,
recordTypeFilter = None,
nameSort = NameSort.ASC
nameSort = NameSort.ASC,
recordOwnerGroupFilter = None
)
.unsafeRunSync()
}

View File

@ -385,14 +385,14 @@ class MySqlRecordSetRepositoryIntegrationSpec
"list record sets" should {
"return all record sets in a zone when optional params are not set" in {
val existing = insert(okZone, 10).map(_.recordSet)
val found = repo.listRecordSets(Some(okZone.id), None, None, None, None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.ASC).unsafeRunSync()
found.recordSets should contain theSameElementsAs existing.map(r => recordSetWithFQDN(r, okZone))
}
"return record sets after the startFrom when set" in {
// load 5, start after the 3rd, we should get back the last two
val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name)
val startFrom = Some(PagingKey.toNextId(existing(2), true))
val found = repo.listRecordSets(Some(okZone.id), startFrom, None, None,None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), startFrom, None, None, None, None, NameSort.ASC).unsafeRunSync()
(found.recordSets should contain).theSameElementsInOrderAs(existing.drop(3)
.map(r => recordSetWithFQDN(r, okZone)))
@ -401,7 +401,7 @@ class MySqlRecordSetRepositoryIntegrationSpec
// load 5, start after the 2nd, take 2, we should get back the 3rd and 4th
val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name)
val startFrom = Some(PagingKey.toNextId(existing(1), true))
val found = repo.listRecordSets(Some(okZone.id), startFrom, Some(2), None, None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), startFrom, Some(2), None, None, None, NameSort.ASC).unsafeRunSync()
(found.recordSets should contain).theSameElementsInOrderAs(existing.slice(2, 4)
.map(r => recordSetWithFQDN(r, okZone)))
@ -421,7 +421,7 @@ class MySqlRecordSetRepositoryIntegrationSpec
val startFrom = Some(PagingKey.toNextId(newRecordSets(1), true))
val found = repo.listRecordSets(
Some(okZone.id), startFrom, Some(3), Some("*z*"), None, NameSort.ASC
Some(okZone.id), startFrom, Some(3), Some("*z*"), None, None, NameSort.ASC
).unsafeRunSync()
(found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames)
}
@ -437,7 +437,7 @@ class MySqlRecordSetRepositoryIntegrationSpec
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
insert(changes)
val found = repo.listRecordSets(Some(okZone.id), None, Some(3), Some("aa*"), None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), None, Some(3), Some("aa*"), None, None, NameSort.ASC).unsafeRunSync()
(found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames)
}
"return record sets using ends with wildcard" in {
@ -452,7 +452,7 @@ class MySqlRecordSetRepositoryIntegrationSpec
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
insert(changes)
val found = repo.listRecordSets(Some(okZone.id), None, Some(3), Some("*b"), None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), None, Some(3), Some("*b"), None, None, NameSort.ASC).unsafeRunSync()
(found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames)
}
"return record sets exact match with no wildcards" in {
@ -468,35 +468,35 @@ class MySqlRecordSetRepositoryIntegrationSpec
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
insert(changes)
val found = repo.listRecordSets(Some(okZone.id), None, Some(3), Some("aaa"), None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), None, Some(3), Some("aaa"), None, None, NameSort.ASC).unsafeRunSync()
(found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames)
}
"return select types of recordsets in a zone" in {
insert(okZone, 10).map(_.recordSet)
val found = repo.listRecordSets(Some(okZone.id), None, None, None, Some(Set(CNAME)), NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), None, None, None, Some(Set(CNAME)), None, NameSort.ASC).unsafeRunSync()
found.recordSets shouldBe List()
found.recordTypeFilter shouldBe Some(Set(CNAME))
}
"return all recordsets in a zone in descending order" in {
val existing = insert(okZone, 10).map(_.recordSet)
val found = repo.listRecordSets(Some(okZone.id), None, None, None, None, NameSort.DESC).unsafeRunSync()
val found = repo.listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.DESC).unsafeRunSync()
found.recordSets should contain theSameElementsAs existing.map(r => recordSetWithFQDN(r, okZone))
found.nameSort shouldBe NameSort.DESC
}
"pages through the list properly" in {
// load 5 records, pages of 2, last page should have 1 result and no next id
val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name)
val page1 = repo.listRecordSets(Some(okZone.id), None, Some(2), None, None, NameSort.ASC).unsafeRunSync()
val page1 = repo.listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC).unsafeRunSync()
(page1.recordSets should contain).theSameElementsInOrderAs(existing.slice(0, 2)
.map(r => recordSetWithFQDN(r, okZone)))
page1.nextId shouldBe Some(PagingKey.toNextId(page1.recordSets(1), true))
val page2 = repo.listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, NameSort.ASC).unsafeRunSync()
val page2 = repo.listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC).unsafeRunSync()
(page2.recordSets should contain).theSameElementsInOrderAs(existing.slice(2, 4)
.map(r => recordSetWithFQDN(r, okZone)))
page2.nextId shouldBe Some(PagingKey.toNextId(page2.recordSets(1), true))
val page3 = repo.listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, NameSort.ASC).unsafeRunSync()
val page3 = repo.listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC).unsafeRunSync()
(page3.recordSets should contain).theSameElementsInOrderAs(existing.slice(4, 5)
.map(r => recordSetWithFQDN(r, okZone)))
page3.nextId shouldBe None
@ -514,33 +514,33 @@ class MySqlRecordSetRepositoryIntegrationSpec
insert(editedChanges)
val existing = editedChanges.map(_.recordSet)
val page1 = repo.listRecordSets(Some(okZone.id), None, Some(2), None, None, NameSort.ASC).unsafeRunSync()
val page1 = repo.listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC).unsafeRunSync()
(page1.recordSets should contain).theSameElementsInOrderAs(List(
recordSetWithFQDN(existing.head, okZone),
recordSetWithFQDN(existing(1), okZone)))
page1.nextId shouldBe Some(PagingKey.toNextId(page1.recordSets.last, true))
val page2 = repo.listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, NameSort.ASC).unsafeRunSync()
val page2 = repo.listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC).unsafeRunSync()
(page2.recordSets should contain).theSameElementsInOrderAs(List(
recordSetWithFQDN(existing(2), okZone), recordSetWithFQDN(existing(3), okZone)))
page2.nextId shouldBe Some(PagingKey.toNextId(page2.recordSets.last, true))
val page3 = repo.listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, NameSort.ASC).unsafeRunSync()
val page3 = repo.listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC).unsafeRunSync()
(page3.recordSets should contain).theSameElementsInOrderAs(List(recordSetWithFQDN(existing(4), okZone)))
page3.nextId shouldBe None
}
"return applicable recordsets in ascending order when recordNameFilter is given" in {
val existing = insert(okZone, 10).map(_.recordSet)
val found = repo.listRecordSets(None, None, None, Some("*.ok*"), None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(None, None, None, Some("*.ok*"), None, None, NameSort.ASC).unsafeRunSync()
found.recordSets should contain theSameElementsAs existing.map(r => recordSetWithFQDN(r, okZone))
}
"return applicable recordsets in descending order when recordNameFilter is given and name sort is descending" in {
val existing = insert(okZone, 10).map(_.recordSet)
val found = repo.listRecordSets(None, None, None, Some("*.ok*"), None, NameSort.DESC).unsafeRunSync()
val found = repo.listRecordSets(None, None, None, Some("*.ok*"), None, None, NameSort.DESC).unsafeRunSync()
found.recordSets should contain theSameElementsAs existing.map(r => recordSetWithFQDN(r, okZone)).reverse
}
"return no recordsets when no zoneId or recordNameFilter are given" in {
val found = repo.listRecordSets(None, None, None, None, None, NameSort.ASC).unsafeRunSync()
val found = repo.listRecordSets(None, None, None, None, None, None, NameSort.ASC).unsafeRunSync()
found.recordSets shouldBe empty
}
}

View File

@ -176,16 +176,20 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored {
maxItems: Option[Int],
recordNameFilter: Option[String],
recordTypeFilter: Option[Set[RecordType]],
recordOwnerGroupFilter: Option[String],
nameSort: NameSort
): IO[ListRecordSetResults] =
monitor("repo.RecordSet.listRecordSets") {
IO {
DB.readOnly { implicit s =>
val maxPlusOne = maxItems.map(_ + 1)
// setup optional filters
val zoneAndNameFilters = (zoneId, recordNameFilter) match {
case (Some(zId), Some(rName)) =>
Some(s"""WHERE zone_id = '$zId' AND name LIKE '${rName.replace('*', '%')}' """)
case (None, Some(fqdn)) => Some(s"""WHERE fqdn LIKE '${fqdn.replace('*', '%')}' """)
case (Some(zId), None) => Some(s"""WHERE zone_id = '$zId' """)
Some(s"zone_id = '$zId' AND name LIKE '${rName.replace('*', '%')}' ")
case (None, Some(fqdn)) => Some(s"fqdn LIKE '${fqdn.replace('*', '%')}' ")
case (Some(zId), None) => Some(s"zone_id = '$zId' ")
case _ => None
}
@ -196,40 +200,49 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored {
val sortBy = (searchByZone, nameSort) match {
case (true, NameSort.DESC) =>
pagingKey.as(
"AND ((name <= {startFromName} AND type > {startFromType}) OR name < {startFromName})"
"((name <= {startFromName} AND type > {startFromType}) OR name < {startFromName})"
)
case (false, NameSort.ASC) =>
pagingKey.as(
"AND ((fqdn >= {startFromName} AND type > {startFromType}) OR fqdn > {startFromName})"
"((fqdn >= {startFromName} AND type > {startFromType}) OR fqdn > {startFromName})"
)
case (false, NameSort.DESC) =>
pagingKey.as(
"AND ((fqdn <= {startFromName} AND type > {startFromType}) OR fqdn < {startFromName})"
"((fqdn <= {startFromName} AND type > {startFromType}) OR fqdn < {startFromName})"
)
case _ =>
pagingKey.as(
"AND ((name >= {startFromName} AND type > {startFromType}) OR name > {startFromName})"
"((name >= {startFromName} AND type > {startFromType}) OR name > {startFromName})"
)
}
val typeFilter = recordTypeFilter.map { t =>
val list = t.map(fromRecordType).mkString(",")
s"""AND type IN ($list)"""
s"type IN ($list)"
}
val maxPlusOne = maxItems.map(_ + 1)
val ownerGroupFilter =
recordOwnerGroupFilter.map(owner => s"owner_group_id = '$owner' ")
val opts = (zoneAndNameFilters ++ sortBy ++ typeFilter ++
Some(s"""ORDER BY fqdn ${nameSort.toString}, type ASC""") ++
maxPlusOne.as("LIMIT {maxItems}")).toList.mkString(" ")
val opts =
(zoneAndNameFilters ++ sortBy ++ typeFilter ++ ownerGroupFilter).toList
val qualifiers = new StringBuilder()
qualifiers.append(s" ORDER BY fqdn ${nameSort.toString}, type ASC ")
maxPlusOne.foreach(limit => qualifiers.append(s"LIMIT $limit"))
val params = (pagingKey.map(pk => 'startFromName -> pk.recordName) ++
pagingKey.map(pk => 'startFromType -> pk.recordType) ++
maxPlusOne.map(m => 'maxItems -> m)).toSeq
pagingKey.map(pk => 'startFromType -> pk.recordType)).toSeq
val query = "SELECT data, fqdn FROM recordset " + opts
// construct query
val query = new StringBuilder()
query.append("SELECT data, fqdn FROM recordset")
if (opts.nonEmpty) {
query.append(" WHERE ").append(opts.mkString(" AND "))
}
query.append(qualifiers)
val results = SQL(query)
val results = SQL(query.toString())
.bindByName(params: _*)
.map(toRecordSet)
.list()

View File

@ -85,11 +85,25 @@
<input id="record-search-text" ng-model="query" type="text" class="form-control" placeholder="Record Name">
</div>
<div class="dropdown">
<a class="record-type-filter-heading force-cursor dropdown-toggle" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">Filter By Record Type <i class="fa fa-chevron-down"></i></a>
<ul class="dropdown-menu record-type-filters" aria-labelledby="dropdownMenu1">
<li ng-repeat="recordType in readRecordTypes"><input type="checkbox" ng-checked="selectedRecordTypes.indexOf(recordType) != -1" ng-click="toggleCheckedRecordType(recordType)"> {{recordType}}</li>
</ul>
<a class="record-type-filter-heading force-cursor dropdown-toggle" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">Filters<i class="fa fa-chevron-down"></i></a>
<div class="dropdown-menu" aria-labelledby="dropdownMenu1">
Record Types
<div>
<ul class="record-type-filters">
<li ng-repeat="recordType in readRecordTypes" style="list-style-type:none;"><input type="checkbox" ng-checked="selectedRecordTypes.indexOf(recordType) != -1" ng-click="toggleCheckedRecordType(recordType)"> {{recordType}}</li>
</ul>
</div>
<div>
Record Owner Group
<div>
<select class="form-control" id="recordOwnerGroup" ng-model="ownerGroupFilter" ng-options="group.id as group.name for group in groups | orderBy: 'name'" ng-click="$event.stopPropagation();">
<option value=""></option>
</select>
</div>
</div>
</div>
</div>
</form>
</div>
</div>

View File

@ -18,7 +18,7 @@
'use strict';
angular.module('recordset')
.controller('RecordSetsController', function($scope, $log, $location, $timeout, recordsService, utilityService, pagingService){
.controller('RecordSetsController', function($scope, $log, $location, $timeout, recordsService, utilityService, pagingService, groupsService){
$scope.recordSet = {};
$scope.recordSetChanges = {};
@ -27,6 +27,7 @@
$scope.nameSortSymbol = "fa-chevron-up";
$scope.readRecordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', "SOA", 'SRV', 'NAPTR', 'SSHFP', 'TXT'];
$scope.selectedRecordTypes = [];
$scope.groups = [];
// paging status for recordsets
var recordsPaging = pagingService.getNewPagingParams(100);
@ -40,13 +41,21 @@
}
return recordsService
.listRecordSets(recordsPaging.maxItems, undefined, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort)
.listRecordSets(recordsPaging.maxItems, undefined, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort, $scope.ownerGroupFilter)
.then(success)
.catch(function (error) {
handleError(error, 'dnsChangesService::getRecordSet-failure');
});
};
groupsService.getGroups(true)
.then(function (results) {
$scope.groups = results['data']['groups'];
})
.catch(function (error) {
handleError(error, 'groupsService::getGroups-failure');
});
function handleError(error, type) {
console.log(error);
var alert = utilityService.failure(error, type);
@ -106,7 +115,7 @@
$scope.prevPage = function() {
var startFrom = pagingService.getPrevStartFrom(recordsPaging);
return recordsService
.listRecordSets(recordsPaging.maxItems, startFrom, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort)
.listRecordSets(recordsPaging.maxItems, startFrom, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort, $scope.recordOwnerGroupFilter)
.then(function(response) {
recordsPaging = pagingService.prevPageUpdate(response.data.nextId, recordsPaging);
updateRecordDisplay(response.data.recordSets);
@ -118,7 +127,7 @@
$scope.nextPage = function() {
return recordsService
.listRecordSets(recordsPaging.maxItems, recordsPaging.next, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort)
.listRecordSets(recordsPaging.maxItems, recordsPaging.next, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort, $scope.recordOwnerGroupFilter)
.then(function(response) {
var recordSets = response.data.recordSets;
recordsPaging = pagingService.nextPageUpdate(recordSets, response.data.nextId, recordsPaging);

View File

@ -19,22 +19,28 @@
angular.module('service.records', [])
.service('recordsService', function ($http, utilityService) {
this.listRecordSets = function (limit, startFrom, nameFilter, typeFilter, nameSort) {
this.listRecordSets = function (limit, startFrom, nameFilter, typeFilter, nameSort, ownerGroupFilter) {
if (typeFilter == "") {
typeFilter = null;
}
if (nameSort == "") {
nameSort = null;
}
if (ownerGroupFilter == "") {
ownerGroupFilter = null;
}
var params = {
"maxItems": limit,
"startFrom": startFrom,
"recordNameFilter": nameFilter,
"recordTypeFilter": typeFilter,
"nameSort": nameSort
"nameSort": nameSort,
"recordOwnerGroupFilter": ownerGroupFilter
};
var url = utilityService.urlBuilder("/api/recordsets", params);
return $http.get(url);
return $http.get(url)
};
this.listRecordSetsByZone = function (id, limit, startFrom, nameFilter, typeFilter, nameSort) {