~jan0sch/smederee
Showing details for patch 48fc3bde8b6ce6180ab085cc23a9e6c70f6255eb.
diff -rN -u old-smederee/modules/tickets/src/main/scala/de/smederee/tickets/DoobieMilestoneRepository.scala new-smederee/modules/tickets/src/main/scala/de/smederee/tickets/DoobieMilestoneRepository.scala --- old-smederee/modules/tickets/src/main/scala/de/smederee/tickets/DoobieMilestoneRepository.scala 2025-01-15 09:32:36.548509128 +0000 +++ new-smederee/modules/tickets/src/main/scala/de/smederee/tickets/DoobieMilestoneRepository.scala 2025-01-15 09:32:36.552509135 +0000 @@ -17,7 +17,11 @@ package de.smederee.tickets +import java.util.UUID + import cats.effect._ +import cats.syntax.all._ +import doobie.Fragments._ import doobie._ import doobie.implicits._ import doobie.postgres.implicits._ @@ -29,10 +33,39 @@ given LogHandler[F] = Slf4jLogHandler.createLogHandler[F](log) + given Meta[AssigneeId] = Meta[UUID].timap(AssigneeId.apply)(_.toUUID) + given Meta[AssigneeName] = Meta[String].timap(AssigneeName.apply)(_.toString) + given Meta[ColourCode] = Meta[String].timap(ColourCode.apply)(_.toString) + given Meta[LabelDescription] = Meta[String].timap(LabelDescription.apply)(_.toString) + given Meta[LabelId] = Meta[Long].timap(LabelId.apply)(_.toLong) + given Meta[LabelName] = Meta[String].timap(LabelName.apply)(_.toString) given Meta[MilestoneDescription] = Meta[String].timap(MilestoneDescription.apply)(_.toString) given Meta[MilestoneId] = Meta[Long].timap(MilestoneId.apply)(_.toLong) given Meta[MilestoneTitle] = Meta[String].timap(MilestoneTitle.apply)(_.toString) given Meta[ProjectId] = Meta[Long].timap(ProjectId.apply)(_.toLong) + given Meta[SubmitterId] = Meta[UUID].timap(SubmitterId.apply)(_.toUUID) + given Meta[SubmitterName] = Meta[String].timap(SubmitterName.apply)(_.toString) + given Meta[TicketContent] = Meta[String].timap(TicketContent.apply)(_.toString) + given Meta[TicketId] = Meta[Long].timap(TicketId.apply)(_.toLong) + given Meta[TicketNumber] = Meta[Int].timap(TicketNumber.apply)(_.toInt) + given Meta[TicketResolution] = Meta[String].timap(TicketResolution.valueOf)(_.toString) + given Meta[TicketStatus] = Meta[String].timap(TicketStatus.valueOf)(_.toString) + given Meta[TicketTitle] = Meta[String].timap(TicketTitle.apply)(_.toString) + + private val selectTicketColumns = + fr"""SELECT + "tickets".number AS number, + "tickets".title AS title, + "tickets".content AS content, + "tickets".status AS status, + "tickets".resolution AS resolution, + "submitters".uid AS submitter_uid, + "submitters".name AS submitter_name, + "tickets".created_at AS created_at, + "tickets".updated_at AS updated_at + FROM "tickets"."tickets" AS "tickets" + LEFT OUTER JOIN "tickets"."users" AS "submitters" + ON "tickets".submitter = "submitters".uid""" override def allMilestones(projectId: ProjectId): Stream[F, Milestone] = sql"""SELECT id, title, description, due_date FROM "tickets"."milestones" WHERE project = $projectId ORDER BY due_date ASC, title ASC""" @@ -40,6 +73,29 @@ .stream .transact(tx) + override def allTickets(filter: Option[TicketFilter])(milestoneId: MilestoneId): Stream[F, Ticket] = { + val milestoneFilter = + fr""""tickets".id IN (SELECT ticket FROM "tickets".milestone_tickets AS "milestone_tickets" WHERE milestone = $milestoneId)""" + val tickets = filter match { + case None => selectTicketColumns ++ whereAnd(milestoneFilter) + case Some(filter) => + val numberFilter = filter.number.toNel.map(numbers => Fragments.in(fr""""tickets".number""", numbers)) + val statusFilter = filter.status.toNel.map(status => Fragments.in(fr""""tickets".status""", status)) + val resolutionFilter = + filter.resolution.toNel.map(resolutions => Fragments.in(fr""""tickets".resolution""", resolutions)) + val submitterFilter = + filter.submitter.toNel.map(submitters => Fragments.in(fr""""submitters".name""", submitters)) + selectTicketColumns ++ whereAndOpt( + milestoneFilter.some, + numberFilter, + statusFilter, + resolutionFilter, + submitterFilter + ) + } + tickets.query[Ticket].stream.transact(tx) + } + override def createMilestone(projectId: ProjectId)(milestone: Milestone): F[Int] = sql"""INSERT INTO "tickets"."milestones" ( diff -rN -u old-smederee/modules/tickets/src/main/scala/de/smederee/tickets/MilestoneRepository.scala new-smederee/modules/tickets/src/main/scala/de/smederee/tickets/MilestoneRepository.scala --- old-smederee/modules/tickets/src/main/scala/de/smederee/tickets/MilestoneRepository.scala 2025-01-15 09:32:36.548509128 +0000 +++ new-smederee/modules/tickets/src/main/scala/de/smederee/tickets/MilestoneRepository.scala 2025-01-15 09:32:36.552509135 +0000 @@ -35,6 +35,17 @@ */ def allMilestones(projectId: ProjectId): Stream[F, Milestone] + /** Return all tickets associated with the given milestone. + * + * @param filter + * A ticket filter containing possible values which will be used to filter the list of tickets. + * @param milestoneId + * The unique internal ID of a milestone for which all tickets shall be returned. + * @return + * A stream of tickets associated with the vcs repository which may be empty. + */ + def allTickets(filter: Option[TicketFilter])(milestoneId: MilestoneId): Stream[F, Ticket] + /** Create a database entry for the given milestone definition. * * @param projectId diff -rN -u old-smederee/modules/tickets/src/test/scala/de/smederee/tickets/DoobieMilestoneRepositoryTest.scala new-smederee/modules/tickets/src/test/scala/de/smederee/tickets/DoobieMilestoneRepositoryTest.scala --- old-smederee/modules/tickets/src/test/scala/de/smederee/tickets/DoobieMilestoneRepositoryTest.scala 2025-01-15 09:32:36.548509128 +0000 +++ new-smederee/modules/tickets/src/test/scala/de/smederee/tickets/DoobieMilestoneRepositoryTest.scala 2025-01-15 09:32:36.552509135 +0000 @@ -102,6 +102,54 @@ } } + test("allTickets must return all tickets associated with the milestone".tag(NeedsDatabase)) { + (genProjectOwner.sample, genProject.sample, genMilestone.sample, genTickets.sample) match { + case (Some(owner), Some(generatedProject), Some(milestone), Some(rawTickets)) => + val project = generatedProject.copy(owner = owner) + val tickets = rawTickets.map(_.copy(submitter = None)) + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val milestoneRepo = new DoobieMilestoneRepository[IO](tx) + val ticketRepo = new DoobieTicketRepository[IO](tx) + val test = for { + _ <- createProjectOwner(owner) + _ <- createTicketsProject(project) + repoId <- loadProjectId(owner.uid, project.name) + foundTickets <- repoId match { + case None => IO.pure(List.empty) + case Some(projectId) => + for { + _ <- milestoneRepo.createMilestone(projectId)(milestone) + _ <- tickets.traverse(ticket => ticketRepo.createTicket(projectId)(ticket)) + createdMilestone <- milestoneRepo.findMilestone(projectId)(milestone.title) + _ <- createdMilestone match { + case None => IO.pure(List.empty) + case Some(milestone) => + tickets.traverse(ticket => ticketRepo.addMilestone(projectId)(ticket.number)(milestone)) + } + foundTickets <- createdMilestone.map(_.id).getOrElse(None) match { + case None => IO.pure(List.empty) + case Some(milestoneId) => milestoneRepo.allTickets(None)(milestoneId).compile.toList + } + } yield foundTickets + } + } yield foundTickets + test.map { foundTickets => + assertEquals(foundTickets.size, tickets.size, "Different number of tickets!") + foundTickets.sortBy(_.number).zip(tickets.sortBy(_.number)).map { (found, expected) => + assertEquals(found.copy(createdAt = expected.createdAt, updatedAt = expected.updatedAt), expected) + } + } + case _ => fail("Could not generate data samples!") + } + } + test("createMilestone must create the milestone".tag(NeedsDatabase)) { (genProjectOwner.sample, genProject.sample, genMilestone.sample) match { case (Some(owner), Some(generatedProject), Some(milestone)) =>