Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APIS-7380 Add XML Vendors to developers CSV export #667

Merged
merged 4 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ class XmlServicesConnector @Inject() (config: Config, http: HttpClientV2)(implic
http.get(url"$baseUrl/organisations?$queryParams").execute[List[XmlOrganisation]]
}

def getAllOrganisations()(implicit hc: HeaderCarrier): Future[List[XmlOrganisation]] = {
val sortByParams = Seq("sortBy" -> "VENDOR_ID")
val queryParams = sortByParams

http.get(url"$baseUrl/organisations?$queryParams").execute[List[XmlOrganisation]]
}

def removeCollaboratorsForUserId(userId: UserId, gatekeeperUser: String)(implicit hc: HeaderCarrier): Future[RemoveAllCollaboratorsForUserIdResult] = {
val request = RemoveAllCollaboratorsForUserIdRequest(userId, gatekeeperUser)
http.post(url"$baseUrl/organisations/all/remove-collaborators")
Expand Down
32 changes: 21 additions & 11 deletions app/uk/gov/hmrc/gatekeeper/controllers/DevelopersController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ import uk.gov.hmrc.apiplatform.modules.tpd.mfa.domain.models.MfaType
import uk.gov.hmrc.gatekeeper.config.AppConfig
import uk.gov.hmrc.gatekeeper.models.Forms.RemoveEmailPreferencesForm
import uk.gov.hmrc.gatekeeper.models._
import uk.gov.hmrc.gatekeeper.services.{ApiDefinitionService, DeveloperService}
import uk.gov.hmrc.gatekeeper.models.xml.XmlOrganisation
import uk.gov.hmrc.gatekeeper.services.{ApiDefinitionService, DeveloperService, XmlService}
import uk.gov.hmrc.gatekeeper.utils.CsvHelper.ColumnDefinition
import uk.gov.hmrc.gatekeeper.utils.{CsvHelper, ErrorHelper, UserFunctionsWrapper}
import uk.gov.hmrc.gatekeeper.views.html.developers.{DevelopersView, _}
Expand All @@ -46,6 +47,7 @@ class DevelopersController @Inject() (
val forbiddenView: ForbiddenView,
developerService: DeveloperService,
val apiDefinitionService: ApiDefinitionService,
xmlService: XmlService,
mcc: MessagesControllerComponents,
developersView: DevelopersView,
removeEmailPreferencesView: RemoveEmailPreferences,
Expand All @@ -67,22 +69,23 @@ class DevelopersController @Inject() (
def developersCsv() = atLeastSuperUserAction { implicit request =>
{

def isMfaTypeActive(user: RegisteredUser, mfaType: MfaType): Boolean = {
def isMfaTypeActive(user: RegisteredUser, mfaType: MfaType): Boolean = {
user.mfaDetails.exists(mfa => (mfa.verified && mfa.mfaType == mfaType))
}

def topicSubscribedTo(user: RegisteredUser, topic: EmailTopic): Boolean = {
def getNumberOfXmlOrganisations(user: RegisteredUser, orgs: List[XmlOrganisation]): Int = {
orgs.filter(org => org.collaborators.exists(coll => coll.userId == user.userId)).size
}
def topicSubscribedTo(user: RegisteredUser, topic: EmailTopic): Boolean = {
user.emailPreferences.topics.contains(topic)
}

def categoriesSubscribedTo(user: RegisteredUser): Int = {
def categoriesSubscribedTo(user: RegisteredUser): Int = {
user.emailPreferences.interests.count(r => r.services.isEmpty)
}
def individualApisSubscribedTo(user: RegisteredUser): Int = {
def individualApisSubscribedTo(user: RegisteredUser): Int = {
user.emailPreferences.interests.map(r => r.services.size).sum
}

val csvColumnDefinitions = Seq[ColumnDefinition[RegisteredUser]](
def csvColumnDefinitions(orgs: List[XmlOrganisation]) = Seq[ColumnDefinition[RegisteredUser]](
ColumnDefinition("UserId", (dev => dev.userId.toString())),
ColumnDefinition("SMS MFA Active", (dev => isMfaTypeActive(dev, MfaType.SMS).toString())),
ColumnDefinition("Authenticator MFA Active", (dev => isMfaTypeActive(dev, MfaType.AUTHENTICATOR_APP).toString())),
Expand All @@ -91,11 +94,18 @@ class DevelopersController @Inject() (
ColumnDefinition("Release Schedules Email", (dev => topicSubscribedTo(dev, RELEASE_SCHEDULES).toString())),
ColumnDefinition("Event Invites Email", (dev => topicSubscribedTo(dev, EVENT_INVITES).toString())),
ColumnDefinition("Full Category Emails", (dev => categoriesSubscribedTo(dev).toString())),
ColumnDefinition("Individual APIs Emails", (dev => individualApisSubscribedTo(dev).toString()))
ColumnDefinition("Individual APIs Emails", (dev => individualApisSubscribedTo(dev).toString())),
ColumnDefinition("XML Vendors", (dev => getNumberOfXmlOrganisations(dev, orgs).toString()))
)

developerService.fetchUsers
.map(users => CsvHelper.toCsvString(csvColumnDefinitions, users.filter(_.verified)))
(
for {
allUsers <- developerService.fetchUsers()
users = allUsers.filter(_.verified)
orgs <- xmlService.getAllOrganisations()
} yield (users, orgs)
)
.map(usersAndOrgs => CsvHelper.toCsvString(csvColumnDefinitions(usersAndOrgs._2), usersAndOrgs._1))
.map(Ok(_).withHeaders(CONTENT_DISPOSITION -> s"attachment; filename=developers-${Instant.now()}.csv").as("text/csv"))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class EmailsController @Inject() (
}

def emailAllUsersPage(): Action[AnyContent] = anyStrideUserAction { implicit request =>
developerService.fetchUsers
developerService.fetchUsers()
.map((users: List[RegisteredUser]) => {
val filteredUsers = users.filter(_.verified)
Ok(emailsAllUsersView(filteredUsers, usersToEmailCopyText(filteredUsers)))
Expand Down
10 changes: 9 additions & 1 deletion app/uk/gov/hmrc/gatekeeper/models/xml/XmlOrganisation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package uk.gov.hmrc.gatekeeper.models.xml

import play.api.libs.json.{Format, Json, OFormat}

import uk.gov.hmrc.apiplatform.modules.common.domain.models.{LaxEmailAddress, UserId}

case class OrganisationId(value: java.util.UUID) extends AnyVal

object OrganisationId {
Expand All @@ -30,7 +32,13 @@ object VendorId {
implicit val formatVendorId: Format[VendorId] = Json.valueFormat[VendorId]
}

case class XmlOrganisation(organisationId: OrganisationId, vendorId: VendorId, name: String)
case class Collaborator(userId: UserId, email: LaxEmailAddress)

object Collaborator {
implicit val formatCollaborator: OFormat[Collaborator] = Json.format[Collaborator]
}

case class XmlOrganisation(organisationId: OrganisationId, vendorId: VendorId, name: String, collaborators: List[Collaborator])

object XmlOrganisation {
implicit val formatOrganisation: OFormat[XmlOrganisation] = Json.format[XmlOrganisation]
Expand Down
2 changes: 1 addition & 1 deletion app/uk/gov/hmrc/gatekeeper/services/DeveloperService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class DeveloperService @Inject() (
})
}

def fetchUsers(implicit hc: HeaderCarrier): Future[List[RegisteredUser]] = {
def fetchUsers()(implicit hc: HeaderCarrier): Future[List[RegisteredUser]] = {
developerConnector.fetchAll().map(_.sortBy(_.sortField))
}

Expand Down
4 changes: 4 additions & 0 deletions app/uk/gov/hmrc/gatekeeper/services/XmlService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class XmlService @Inject() (xmlServicesConnector: XmlServicesConnector)(implicit
xmlServicesConnector.findOrganisationsByUserId(userId)
}

def getAllOrganisations()(implicit hc: HeaderCarrier): Future[List[XmlOrganisation]] = {
xmlServicesConnector.getAllOrganisations()
}

def removeCollaboratorsForUserId(userId: UserId, gatekeeperUser: String)(implicit hc: HeaderCarrier): Future[RemoveAllCollaboratorsForUserIdResult] = {
xmlServicesConnector.removeCollaboratorsForUserId(userId, gatekeeperUser)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import uk.gov.hmrc.http.{HeaderCarrier, UpstreamErrorResponse}

import uk.gov.hmrc.apiplatform.modules.common.domain.models.{ApplicationId, UserId, _}
import uk.gov.hmrc.apiplatform.modules.common.utils._
import uk.gov.hmrc.gatekeeper.models.xml.{OrganisationId, VendorId, XmlApi, XmlOrganisation}
import uk.gov.hmrc.gatekeeper.models.xml.{Collaborator, OrganisationId, VendorId, XmlApi, XmlOrganisation}
import uk.gov.hmrc.gatekeeper.models.{RemoveAllCollaboratorsForUserIdFailureResult, RemoveAllCollaboratorsForUserIdRequest, RemoveAllCollaboratorsForUserIdSuccessResult}
import uk.gov.hmrc.gatekeeper.utils.UrlEncoding

Expand Down Expand Up @@ -186,7 +186,7 @@ class XmlServicesConnectorSpec
"findOrganisationsByUserId" should {
val url = "/api-platform-xml-services/organisations"
val userId = UserId.random
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()))
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()), collaborators = List.empty)

"return APIs when userId exists on an organisation" in new Setup {
stubFor(
Expand Down Expand Up @@ -227,6 +227,38 @@ class XmlServicesConnectorSpec
}
}

"getAllOrganisations" should {
val url = "/api-platform-xml-services/organisations"
val coll = Collaborator(UserId.random, LaxEmailAddress("[email protected]"))
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()), collaborators = List(coll))

"return organisations" in new Setup {
stubFor(
get(urlEqualTo(s"$url?sortBy=VENDOR_ID"))
.willReturn(
aResponse()
.withStatus(OK)
.withBody(Json.toJson(List(orgOne)).toString)
)
)
await(connector.getAllOrganisations()) shouldBe List(orgOne)
}

"return UpstreamErrorResponse when backend returns 400" in new Setup {
stubFor(
get(urlEqualTo(s"$url?sortBy=VENDOR_ID"))
.willReturn(
aResponse()
.withStatus(BAD_REQUEST)
)
)
intercept[UpstreamErrorResponse](await(connector.getAllOrganisations())) match {
case UpstreamErrorResponse(_, BAD_REQUEST, _, _) => succeed
case _ => fail()
}
}
}

"removeCollaboratorsForUserId" should {
val url = "/api-platform-xml-services/organisations/all/remove-collaborators"
val userId = UserId.random
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ trait ControllerSetupBase
with ApiDefinitionServiceMockProvider
with DeveloperServiceMockProvider
with ApplicationServiceMockProvider
with XmlServiceMockProvider
with ApiCataloguePublishConnectorMockProvider
with ApmServiceMockProvider
with DeploymentApprovalServiceMockProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import uk.gov.hmrc.apiplatform.modules.tpd.emailpreferences.domain.models.EmailT
import uk.gov.hmrc.apiplatform.modules.tpd.emailpreferences.domain.models.{EmailPreferences, TaxRegimeInterests}
import uk.gov.hmrc.apiplatform.modules.tpd.mfa.domain.models._
import uk.gov.hmrc.gatekeeper.models._
import uk.gov.hmrc.gatekeeper.models.xml.{Collaborator, OrganisationId, VendorId, XmlOrganisation}
import uk.gov.hmrc.gatekeeper.utils.FakeRequestCSRFSupport._
import uk.gov.hmrc.gatekeeper.views.html.developers._
import uk.gov.hmrc.gatekeeper.views.html.{ErrorTemplate, ForbiddenView}
Expand All @@ -61,6 +62,7 @@ class DevelopersControllerSpec extends ControllerBaseSpec {
forbiddenView,
mockDeveloperService,
mockApiDefinitionService,
mockXmlService,
mcc,
developersView,
removeEmailPref,
Expand Down Expand Up @@ -182,14 +184,28 @@ class DevelopersControllerSpec extends ControllerBaseSpec {
private val user3 =
RegisteredUser(LaxEmailAddress("[email protected]"), userId3, "first", "last", verified = true, mfaDetails = mfaDetails3, emailPreferences = emailPref3)

private val xmlOrg1 = XmlOrganisation(
OrganisationId(UUID.randomUUID()),
VendorId(1),
"xml org name 1",
List(Collaborator(userId3, LaxEmailAddress("[email protected]")))
)
private val xmlOrg2 = XmlOrganisation(
OrganisationId(UUID.randomUUID()),
VendorId(2),
"xml org name 2",
List(Collaborator(userId1, LaxEmailAddress("[email protected]")), Collaborator(userId3, LaxEmailAddress("[email protected]")))
)

DeveloperServiceMock.FetchUsers.returns(user1, user2, user3)
XmlServiceMock.GetAllXmlOrganisations.returns(List(xmlOrg1, xmlOrg2))

val result = developersController.developersCsv()(aLoggedInRequest)
contentAsString(result) should be(
s"UserId,SMS MFA Active,Authenticator MFA Active,Business And Policy Email,Technical Email,Release Schedules Email,Event Invites Email,Full Category Emails,Individual APIs Emails\n" +
s"${userId1.toString},false,false,false,false,false,false,0,3\n" +
s"${userId2.toString},true,false,false,false,false,false,1,0\n" +
s"${userId3.toString},false,true,true,true,true,true,0,0\n"
s"UserId,SMS MFA Active,Authenticator MFA Active,Business And Policy Email,Technical Email,Release Schedules Email,Event Invites Email,Full Category Emails,Individual APIs Emails,XML Vendors\n" +
s"${userId1.toString},false,false,false,false,false,false,0,3,1\n" +
s"${userId2.toString},true,false,false,false,false,false,1,0,0\n" +
s"${userId3.toString},false,true,true,true,true,true,0,0,2\n"
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ trait DeveloperServiceMockProvider {
object FetchUsers {

def returns(users: RegisteredUser*) =
when(mockDeveloperService.fetchUsers(*))
when(mockDeveloperService.fetchUsers()(*))
.thenReturn(successful(users.toList))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ trait XmlServiceMockProvider {
def returnsError() =
when(mockXmlService.findOrganisationsByUserId(*[UserId])(*)).thenThrow(UpstreamErrorResponse("error", 500, 500, Map.empty))
}

object GetAllXmlOrganisations {

def returns(xmlOrganisations: List[XmlOrganisation]) =
when(mockXmlService.getAllOrganisations()(*)).thenReturn(successful(xmlOrganisations))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class DeveloperServiceSpec extends AsyncHmrcSpec with CollaboratorTracker with A
val apiVersion = ApiVersionNbr.random
val organisations = List(DeskproOrganisation(uk.gov.hmrc.gatekeeper.models.organisations.OrganisationId("1"), "org name 1", List.empty))

val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()))
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()), collaborators = List.empty)

val xmlServiceNames = Set("XML API one", "XML API two")
val offset = 0
Expand Down
2 changes: 1 addition & 1 deletion test/uk/gov/hmrc/gatekeeper/services/XmlServiceSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class XmlServiceSpec extends AsyncHmrcSpec {
}

"findOrganisationsByUserId" should {
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()))
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()), collaborators = List.empty)

"Return List of Organisations when call to get xml apis is successful" in new Setup {
XmlServicesConnectorMock.GetOrganisations.returnsOrganisations(user.userId, List(orgOne))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class DeveloperDetailsViewSpec extends CommonViewSpec with ApplicationBuilder {

val xmlServiceNames = Set("XML Service 1", "XML Service 2", "XML Service 3")

val xmlOrganisations = List(XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID())))
val xmlOrganisations = List(XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()), collaborators = List.empty))

val buildXmlServicesFeUrl: (OrganisationId) => String = (organisationId) =>
s"/api-gatekeeper-xml-services/organisations/${organisationId.value}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ object MockDataSugar {
)

val xmlApis = Json.toJson(Seq(xmlApiOne)).toString
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()))
val orgOne = XmlOrganisation(name = "Organisation one", vendorId = VendorId(1), organisationId = OrganisationId(UUID.randomUUID()), collaborators = List.empty)
val xmlOrganisations = Json.toJson(List(orgOne)).toString

val deskproOrganisationId = uk.gov.hmrc.gatekeeper.models.organisations.OrganisationId("1")
Expand Down