Skip to content

Commit

Permalink
Add project and task page
Browse files Browse the repository at this point in the history
  • Loading branch information
larsduelfer committed Jul 8, 2024
1 parent 16a3a1b commit 64ec3c9
Show file tree
Hide file tree
Showing 51 changed files with 2,706 additions and 237 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,21 @@ jobs:
- name: Bundle application
working-directory: ./frontend
run: npm build

frontend-v2:
name: Build new frontend (vue) with npm
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: ./frontend-new/package-lock.json
- name: Install dependencies
working-directory: ./frontend-new
run: npm install
- name: Bundle application
working-directory: ./frontend-new
run: npm run build
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.novatecgmbh.eventsourcing.axon.company.company.graphql

import com.novatecgmbh.eventsourcing.axon.company.company.api.AllCompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyId
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQueryResult
import com.novatecgmbh.eventsourcing.axon.project.participant.api.ParticipantQueryResult
import com.novatecgmbh.eventsourcing.axon.project.project.api.CompanyIdsUserCanCreateProjectsForQuery
import java.util.concurrent.CompletableFuture
import org.axonframework.extensions.kotlin.query
import org.axonframework.extensions.kotlin.queryMany
Expand All @@ -30,6 +31,20 @@ class CompanyQueryController(val queryGateway: QueryGateway) {
queryGateway.let { queryGateway.query(CompanyQuery(participant.companyId)) }

@QueryMapping
fun companies(): CompletableFuture<List<CompanyQueryResult>> =
queryGateway.queryMany(AllCompaniesQuery())
fun companies(
@Argument userAllowedToCreateProjectFor: Boolean
): CompletableFuture<List<CompanyQueryResult>> {
val companies =
if (userAllowedToCreateProjectFor) {
queryGateway.queryMany<CompanyId, CompanyIdsUserCanCreateProjectsForQuery>(
CompanyIdsUserCanCreateProjectsForQuery()
)
} else {
CompletableFuture.completedFuture(emptySet<CompanyId>())
}
.get()
return queryGateway.queryMany<CompanyQueryResult, CompaniesQuery>(
CompaniesQuery(companies.toSet())
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ParticipantQueryController(val queryGateway: QueryGateway) {

// TODO: Change to batch mapping
@SchemaMapping(typeName = "Task")
fun participant(task: TaskQueryResult): CompletableFuture<ParticipantQueryResult?> =
fun assignee(task: TaskQueryResult): CompletableFuture<ParticipantQueryResult?> =
if (task.participantId != null) {
queryGateway.query(ParticipantQuery(task.participantId!!))
} else CompletableFuture.completedFuture(null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package com.novatecgmbh.eventsourcing.axon.project.project.graphql

import com.novatecgmbh.eventsourcing.axon.application.security.RegisteredUserPrincipal
import com.novatecgmbh.eventsourcing.axon.project.project.api.MyProjectsQuery
import com.novatecgmbh.eventsourcing.axon.project.project.api.ProjectId
import com.novatecgmbh.eventsourcing.axon.project.project.api.ProjectQuery
import com.novatecgmbh.eventsourcing.axon.project.project.api.ProjectQueryResult
import com.novatecgmbh.eventsourcing.axon.project.project.api.*
import java.util.concurrent.CompletableFuture
import org.axonframework.extensions.kotlin.query
import org.axonframework.extensions.kotlin.queryMany
import org.axonframework.extensions.kotlin.queryOptional
import org.axonframework.messaging.responsetypes.ResponseTypes
import org.axonframework.queryhandling.QueryGateway
import org.springframework.graphql.data.method.annotation.Argument
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.graphql.data.method.annotation.SchemaMapping
import org.springframework.graphql.data.method.annotation.SubscriptionMapping
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.annotation.AuthenticationPrincipal
Expand All @@ -35,8 +34,8 @@ class ProjectQueryController(val queryGateway: QueryGateway) {
MyProjectsQuery((user.principal as RegisteredUserPrincipal).identifier)
)

@SubscriptionMapping("projects")
fun projectsAndUpdates(
@SubscriptionMapping("project")
fun projectUpdates(
@AuthenticationPrincipal user: UsernamePasswordAuthenticationToken
): Flux<ProjectQueryResult> {
val query =
Expand All @@ -46,10 +45,35 @@ class ProjectQueryController(val queryGateway: QueryGateway) {
ResponseTypes.instanceOf(ProjectQueryResult::class.java)
)

return query
.initialResult()
.flatMapMany { Flux.fromIterable(it) }
.concatWith(query.updates())
.doFinally { query.cancel() }
return query.updates().doFinally { query.cancel() }

// Alternative if this function should also return the current projects before the updates
// return queryl
// .initialResult()
// .flatMapMany { Flux.fromIterable(it) }
// .concatWith(query.updates())
// .doFinally { query.cancel() }
}

@SchemaMapping(typeName = "Project")
fun statistics(project: ProjectQueryResult): CompletableFuture<ProjectStatistics> =
queryGateway
.query<ProjectDetailsQueryResult, ProjectDetailsQuery>(
ProjectDetailsQuery(project.identifier)
)
.thenApply { result ->
ProjectStatistics(
tasks =
TaskStatistics(
all = result.allTasksCount.toInt(),
planned = result.plannedTasksCount.toInt(),
started = result.startedTasksCount.toInt(),
completed = result.completedTasksCount.toInt()
)
)
}
}

data class ProjectStatistics(val tasks: TaskStatistics)

data class TaskStatistics(val all: Int, val planned: Int, val started: Int, val completed: Int)
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.novatecgmbh.eventsourcing.axon.project.task.graphql

import com.novatecgmbh.eventsourcing.axon.project.project.api.ProjectId
import com.novatecgmbh.eventsourcing.axon.project.project.api.ProjectQueryResult
import com.novatecgmbh.eventsourcing.axon.project.task.api.TaskId
import com.novatecgmbh.eventsourcing.axon.project.task.api.TaskQuery
import com.novatecgmbh.eventsourcing.axon.project.task.api.TaskQueryResult
import com.novatecgmbh.eventsourcing.axon.project.task.api.TasksByMultipleProjectsQuery
import com.novatecgmbh.eventsourcing.axon.project.task.api.*
import java.time.LocalDate
import java.util.concurrent.CompletableFuture
import org.axonframework.extensions.kotlin.query
import org.axonframework.extensions.kotlin.queryMany
import org.axonframework.messaging.responsetypes.ResponseTypes
import org.axonframework.queryhandling.QueryGateway
import org.springframework.graphql.data.method.annotation.Argument
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.graphql.data.method.annotation.SchemaMapping
import org.springframework.graphql.data.method.annotation.SubscriptionMapping
import org.springframework.stereotype.Controller
import reactor.core.publisher.Flux

@Controller
class TaskQueryController(val queryGateway: QueryGateway) {
Expand Down Expand Up @@ -43,6 +44,22 @@ class TaskQueryController(val queryGateway: QueryGateway) {
// }
// .toMono()

@QueryMapping
fun tasks(@Argument projectIdentifier: ProjectId): CompletableFuture<List<TaskQueryResult>> =
queryGateway.queryMany(TasksByProjectQuery(projectIdentifier))

@SubscriptionMapping("tasks")
fun taskUpdates(@Argument projectIdentifier: ProjectId): Flux<TaskQueryResult> {
val query =
queryGateway.subscriptionQuery(
TasksByProjectQuery(projectIdentifier),
ResponseTypes.multipleInstancesOf(TaskQueryResult::class.java),
ResponseTypes.instanceOf(TaskQueryResult::class.java)
)

return query.updates().doFinally { query.cancel() }
}

@SchemaMapping(typeName = "Project")
fun tasks(
project: ProjectQueryResult,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.novatecgmbh.eventsourcing.axon.user.user.graphql

import com.novatecgmbh.eventsourcing.axon.application.security.RegisteredUserPrincipal
import com.novatecgmbh.eventsourcing.axon.company.employee.api.EmployeeQueryResult
import com.novatecgmbh.eventsourcing.axon.project.participant.api.ParticipantQueryResult
import com.novatecgmbh.eventsourcing.axon.user.api.AllUsersQuery
Expand All @@ -11,6 +12,8 @@ import org.axonframework.extensions.kotlin.queryMany
import org.axonframework.queryhandling.QueryGateway
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.graphql.data.method.annotation.SchemaMapping
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.stereotype.Controller

@Controller
Expand All @@ -19,6 +22,12 @@ class UserQueryController(val queryGateway: QueryGateway) {
@QueryMapping
fun users(): CompletableFuture<List<UserQueryResult>> = queryGateway.queryMany(AllUsersQuery())

@QueryMapping
fun currentUser(
@AuthenticationPrincipal user: UsernamePasswordAuthenticationToken
): CompletableFuture<UserQueryResult> =
queryGateway.query(UserQuery((user.principal as RegisteredUserPrincipal).identifier))

// TODO: Change to BatchMapping to be more efficient
@SchemaMapping(typeName = "Participant")
fun user(participant: ParticipantQueryResult): CompletableFuture<UserQueryResult> =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extend type Query {
company(identifier:ID!): Company
companies: [Company]
companies(userAllowedToCreateProjectFor: Boolean = false): [Company]
employee(identifier:ID!): Employee
}

Expand Down
20 changes: 18 additions & 2 deletions backend/apis/graphql/src/main/resources/graphql/project.graphqls
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
extend type Query {
projects: [Project]
project(identifier: ID!): Project
tasks(projectIdentifier: ID!): [Task]
task(identifier: ID!): Task
participant(identifier: ID!): Participant
}

type Subscription {
projects: Project
project: Project
tasks(projectIdentifier: ID!): Task
}

extend type Mutation {
Expand Down Expand Up @@ -80,6 +82,7 @@ extend type Mutation {

type Project {
identifier: ID!
version: Int!
name: String!
startDate: String
deadline: String
Expand All @@ -88,16 +91,29 @@ type Project {
actualEndDate: String
tasks(from: Date, to: Date): [Task]
participants: [Participant]
statistics: ProjectStatistics
}

type ProjectStatistics {
tasks: TasksStatistics,
}

type TasksStatistics {
all: Int!,
planned: Int!,
started: Int!,
completed: Int!
}

type Task {
identifier: ID!
version: Int!
name: String!
description: String
startDate: String
endDate: String
status: TaskStatus!
participant: Participant
assignee: Participant
todos: [Todo]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type Query {
users: [User]
currentUser: User
}

type Mutation {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.novatecgmbh.eventsourcing.axon.company.company.web

import com.novatecgmbh.eventsourcing.axon.company.company.api.AllCompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyId
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQueryResult
Expand All @@ -25,13 +25,13 @@ class CompanyController(
) {
@GetMapping
fun getAllCompanies(): CompletableFuture<List<CompanyQueryResult>> =
queryGateway.queryMany(AllCompaniesQuery())
queryGateway.queryMany(CompaniesQuery())

@GetMapping(produces = [APPLICATION_NDJSON_VALUE])
fun getAllCompaniesAndUpdates(): Flux<CompanyQueryResult> {
val query =
queryGateway.subscriptionQuery(
AllCompaniesQuery(),
CompaniesQuery(),
ResponseTypes.multipleInstancesOf(CompanyQueryResult::class.java),
ResponseTypes.instanceOf(CompanyQueryResult::class.java)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.novatecgmbh.eventsourcing.axon.company.company.rsocket

import com.novatecgmbh.eventsourcing.axon.company.company.api.AllCompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyId
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQueryResult
Expand All @@ -25,7 +25,7 @@ class CompanyController(

@MessageMapping("companies")
fun subscribeAllCompaniesUpdates(): Flux<CompanyQueryResult> =
queryGateway.queryUpdates(AllCompaniesQuery(), CompanyQueryResult::class.java)
queryGateway.queryUpdates(CompaniesQuery(), CompanyQueryResult::class.java)

@MessageMapping("companies.{id}")
fun subscribeCompanyByIdUpdates(@DestinationVariable id: CompanyId): Flux<CompanyQueryResult> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.novatecgmbh.eventsourcing.axon.company.company.api

import com.novatecgmbh.eventsourcing.axon.common.api.AggregateReference

class AllCompaniesQuery
class CompaniesQuery(val companyIds: Set<CompanyId> = emptySet())

data class CompanyQuery(val companyId: CompanyId)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyId
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository interface CompanyProjectionRepository : JpaRepository<CompanyProjection, CompanyId>
@Repository
interface CompanyProjectionRepository : JpaRepository<CompanyProjection, CompanyId> {

fun findAllByIdentifierIn(identifier: Set<CompanyId>): List<CompanyProjection>
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CompanyProjector(
query.companyId == company.identifier
}

queryUpdateEmitter.emit<AllCompaniesQuery, CompanyQueryResult>(company.toQueryResult()) {
queryUpdateEmitter.emit<CompaniesQuery, CompanyQueryResult>(company.toQueryResult()) {
true
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.novatecgmbh.eventsourcing.axon.company.company.query

import com.novatecgmbh.eventsourcing.axon.company.company.api.AllCompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompaniesQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQuery
import com.novatecgmbh.eventsourcing.axon.company.company.api.CompanyQueryResult
import java.util.*
Expand All @@ -15,6 +15,11 @@ class CompanyQueryHandler(val repository: CompanyProjectionRepository) {
repository.findById(query.companyId).map { it.toQueryResult() }

@QueryHandler
fun handle(query: AllCompaniesQuery): Iterable<CompanyQueryResult> =
repository.findAll().map { it.toQueryResult() }
fun handle(query: CompaniesQuery): Iterable<CompanyQueryResult> =
if (query.companyIds.isEmpty()) {
repository.findAll()
} else {
repository.findAllByIdentifierIn(query.companyIds)
}
.map { it.toQueryResult() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ data class ProjectDetailsQueryResult(
val startedTasksCount: Long,
val completedTasksCount: Long,
)

class CompanyIdsUserCanCreateProjectsForQuery()
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ data class TaskQuery(val taskId: TaskId)

data class TaskQueryResult(
val identifier: TaskId,
val version: Int,
val projectId: ProjectId,
val name: String,
val description: String?,
Expand Down
Loading

0 comments on commit 64ec3c9

Please sign in to comment.