Skip to content

Commit

Permalink
Merge pull request #128 from ccims/feature/manual-project-layout
Browse files Browse the repository at this point in the history
Feature/manual project layout
  • Loading branch information
nk-coding authored Sep 8, 2024
2 parents 16afec4 + b22c277 commit 155fc9f
Show file tree
Hide file tree
Showing 27 changed files with 680 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import com.expediagroup.graphql.server.operations.Mutation
import graphql.schema.DataFetchingEnvironment
import gropius.authorization.gropiusAuthorizationContext
import gropius.dto.input.architecture.*
import gropius.dto.input.architecture.layout.CreateViewInput
import gropius.dto.input.architecture.layout.UpdateViewInput
import gropius.dto.input.common.DeleteNodeInput
import gropius.dto.payload.AddComponentVersionToProjectPayload
import gropius.dto.payload.DeleteNodePayload
import gropius.graphql.AutoPayloadType
import gropius.model.architecture.*
import gropius.model.architecture.layout.View
import gropius.service.architecture.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.transaction.annotation.Propagation
Expand All @@ -29,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional
* @param imsProjectService used for IMSProject-related mutations
* @param intraComponentDependencySpecificationService used for IntraComponentDependencySpecificationService-related mutations
* @param syncPermissionTargetService used for SyncPermissionTarget-related mutations
* @param viewService used for View-related mutations
*/
@org.springframework.stereotype.Component
@Transactional(propagation = Propagation.REQUIRES_NEW)
Expand All @@ -43,7 +47,8 @@ class ArchitectureMutations(
private val imsService: IMSService,
private val imsProjectService: IMSProjectService,
private val intraComponentDependencySpecificationService: IntraComponentDependencySpecificationService,
private val syncPermissionTargetService: SyncPermissionTargetService
private val syncPermissionTargetService: SyncPermissionTargetService,
private val viewService: ViewService
) : Mutation {

@GraphQLDescription(
Expand Down Expand Up @@ -121,6 +126,33 @@ class ArchitectureMutations(
return DeleteNodePayload(input.id)
}

@GraphQLDescription("Creates a new View, requires MANAGE_VIEWS on the project owning the view.")
@AutoPayloadType("The created View")
suspend fun createView(
@GraphQLDescription("Defines the created View")
input: CreateViewInput, dfe: DataFetchingEnvironment
): View {
return viewService.createView(dfe.gropiusAuthorizationContext, input)
}

@GraphQLDescription("Updates the specified View, requires MANAGE_VIEWS on the project owning the view.")
@AutoPayloadType("The updated View")
suspend fun updateView(
@GraphQLDescription("Defines which View to update and how to update it")
input: UpdateViewInput, dfe: DataFetchingEnvironment
): View {
return viewService.updateView(dfe.gropiusAuthorizationContext, input)
}

@GraphQLDescription("Deletes the specified View, requires MANAGE_VIEWS on the project owning the view.")
suspend fun deleteView(
@GraphQLDescription("Defines which View to delete")
input: DeleteNodeInput, dfe: DataFetchingEnvironment
): DeleteNodePayload {
viewService.deleteView(dfe.gropiusAuthorizationContext, input)
return DeleteNodePayload(input.id)
}

@GraphQLDescription("Creates a new InterfaceSpecification, requires ADMIN on the Component.")
@AutoPayloadType("The created InterfaceSpecification")
suspend fun createInterfaceSpecification(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
package gropius.dto.input.architecture

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.execution.OptionalInput
import com.expediagroup.graphql.generator.scalars.ID
import gropius.dto.input.architecture.layout.UpdateLayoutInput
import gropius.dto.input.architecture.layout.UpdateRelationLayoutInput
import gropius.dto.input.architecture.layout.UpdateRelationPartnerLayoutInput

@GraphQLDescription("Input for the updateProject mutation")
class UpdateProjectInput : UpdateTrackableInput()
class UpdateProjectInput(
@GraphQLDescription("The default view for the project")
val defaultView: OptionalInput<ID?>,
@GraphQLDescription("Defines the new layout of a set of Relations")
override val relationLayouts: OptionalInput<List<UpdateRelationLayoutInput>>,
@GraphQLDescription("Defines the new layout of a set of RelationPartners")
override val relationPartnerLayouts: OptionalInput<List<UpdateRelationPartnerLayoutInput>>,
) : UpdateTrackableInput(), UpdateLayoutInput {

override fun validate() {
super.validate()
validateLayout()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gropius.dto.input.architecture.layout

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.execution.OptionalInput
import com.expediagroup.graphql.generator.scalars.ID
import gropius.dto.input.common.CreateNamedNodeInput

@GraphQLDescription("Input for the createView mutation")
class CreateViewInput(
@GraphQLDescription("Defines the new layout of a set of Relations")
override val relationLayouts: OptionalInput<List<UpdateRelationLayoutInput>>,
@GraphQLDescription("Defines the new layout of a set of RelationPartners")
override val relationPartnerLayouts: OptionalInput<List<UpdateRelationPartnerLayoutInput>>,
@GraphQLDescription("The new filter of the view")
val filterByTemplate: List<ID>,
@GraphQLDescription("The id of the project the view belongs to")
val project: ID
) : CreateNamedNodeInput(), UpdateLayoutInput {

override fun validate() {
super.validate()
validateLayout()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gropius.dto.input.architecture.layout

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import gropius.dto.input.common.Input
import gropius.model.architecture.layout.Point

@GraphQLDescription("Input which defines the layout of a Relation")
class RelationLayoutInput(
@GraphQLDescription("List of intermediate points of the relation")
val points: List<Point>,
) : Input()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gropius.dto.input.architecture.layout

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import gropius.dto.input.common.Input
import gropius.model.architecture.layout.Point

@GraphQLDescription("Input which defines the layout of a RelationPartner")
class RelationPartnerLayoutInput(
@GraphQLDescription("The position of the RelationPartner")
val pos: Point
) : Input()
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gropius.dto.input.architecture.layout

import com.expediagroup.graphql.generator.execution.OptionalInput
import gropius.dto.input.ifPresent

/**
* Common input for updating the layout of a Project
*/
interface UpdateLayoutInput {

/**
* The layout of the RelationPartners
*/
val relationPartnerLayouts: OptionalInput<List<UpdateRelationPartnerLayoutInput>>

/**
* The layout of the Relations
*/
val relationLayouts: OptionalInput<List<UpdateRelationLayoutInput>>

/**
* Validates the [relationPartnerLayouts] and [relationLayouts]
* Ensures that there is only one layout per RelationPartner and Relation
*/
fun validateLayout() {
relationPartnerLayouts.ifPresent { layouts ->
layouts.forEach(UpdateRelationPartnerLayoutInput::validate)
layouts.groupBy { it.relationPartner }.forEach { (id, group) ->
if (group.size > 1) {
throw IllegalArgumentException("Multiple layouts for the same RelationPartner: $id")
}
}
}
relationLayouts.ifPresent { layouts ->
layouts.forEach(UpdateRelationLayoutInput::validate)
layouts.groupBy { it.relation }.forEach { (id, group) ->
if (group.size > 1) {
throw IllegalArgumentException("Multiple layouts for the same Relation: $id")
}
}
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gropius.dto.input.architecture.layout

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.scalars.ID
import gropius.dto.input.common.Input

@GraphQLDescription("Input to update the layout of a Relation")
class UpdateRelationLayoutInput(
@GraphQLDescription("The id of the Relation of which to update the layout")
val relation: ID,
@GraphQLDescription("The new layout of the Relation, or null if the layout should be reset")
val layout: RelationLayoutInput?
) : Input() {

override fun validate() {
super.validate()
layout?.validate()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gropius.dto.input.architecture.layout

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.scalars.ID
import gropius.dto.input.common.Input

@GraphQLDescription("Input to update the layout of a RelationPartner")
class UpdateRelationPartnerLayoutInput(
@GraphQLDescription("The id of the RelationPartner of which to update the layout")
val relationPartner: ID,
@GraphQLDescription("The new layout of the RelationPartner, or null if the layout should be reset")
val layout: RelationPartnerLayoutInput?
) : Input() {

override fun validate() {
super.validate()
layout?.validate()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gropius.dto.input.architecture.layout

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.execution.OptionalInput
import com.expediagroup.graphql.generator.scalars.ID
import gropius.dto.input.common.UpdateNamedNodeInput

@GraphQLDescription("Input for the updateView mutation")
class UpdateViewInput(
@GraphQLDescription("Defines the new layout of a set of Relations")
override val relationLayouts: OptionalInput<List<UpdateRelationLayoutInput>>,
@GraphQLDescription("Defines the new layout of a set of RelationPartners")
override val relationPartnerLayouts: OptionalInput<List<UpdateRelationPartnerLayoutInput>>,
@GraphQLDescription("The new filter of the view")
val filterByTemplate: OptionalInput<List<ID>>
) : UpdateNamedNodeInput(), UpdateLayoutInput {

override fun validate() {
super.validate()
validateLayout()
}

}
34 changes: 33 additions & 1 deletion core/src/main/kotlin/gropius/model/architecture/Project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package gropius.model.architecture

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import gropius.authorization.RELATED_TO_NODE_PERMISSION_RULE
import gropius.model.architecture.layout.Layout
import gropius.model.architecture.layout.RelationLayout
import gropius.model.architecture.layout.RelationPartnerLayout
import gropius.model.architecture.layout.View
import gropius.model.user.permission.NodePermission
import gropius.model.user.permission.NodeWithPermissions
import gropius.model.user.permission.ProjectPermission
Expand All @@ -21,19 +25,47 @@ import java.net.URI
ProjectPermission.MANAGE_COMPONENTS,
allow = [Rule(RELATED_TO_NODE_PERMISSION_RULE, options = [NodePermission.ADMIN])]
)
@Authorization(
ProjectPermission.MANAGE_VIEWS,
allow = [Rule(RELATED_TO_NODE_PERMISSION_RULE, options = [NodePermission.ADMIN])]
)
class Project(
name: String, description: String, repositoryURL: URI?
) : Trackable(name, description, repositoryURL), NodeWithPermissions<ProjectPermission> {
) : Trackable(name, description, repositoryURL), NodeWithPermissions<ProjectPermission>, Layout {

companion object {
const val COMPONENT = "COMPONENT"
const val RELATION_PARTNER = "RELATION_PARTNER"
const val RELATION = "RELATION"
const val VIEW = "VIEW"
const val DEFAULT_VIEW = "DEFAULT_VIEW"
}

@NodeRelationship(COMPONENT, Direction.OUTGOING)
@GraphQLDescription("The ComponentVersions this consists of.")
@FilterProperty
val components by NodeSetProperty<ComponentVersion>()

@NodeRelationship(RELATION_PARTNER, Direction.OUTGOING)
@GraphQLDescription("Layouts for relation partners")
@FilterProperty
override val relationPartnerLayouts by NodeSetProperty<RelationPartnerLayout>()

@NodeRelationship(RELATION, Direction.OUTGOING)
@GraphQLDescription("Layouts for relations")
@FilterProperty
override val relationLayouts by NodeSetProperty<RelationLayout>()

@NodeRelationship(VIEW, Direction.OUTGOING)
@GraphQLDescription("Views on the architecture graph of this project.")
@FilterProperty
val views by NodeSetProperty<View>()

@NodeRelationship(DEFAULT_VIEW, Direction.OUTGOING)
@GraphQLDescription("The default view for this project.")
@FilterProperty
val defaultView by NodeProperty<View?>()

@NodeRelationship(NodePermission.NODE, Direction.INCOMING)
@GraphQLDescription("Permissions for this Project.")
@FilterProperty
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/kotlin/gropius/model/architecture/Relation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gropius.model.architecture

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
import gropius.model.architecture.layout.RelationLayout
import gropius.model.common.BaseNode
import gropius.model.template.BaseTemplate
import gropius.model.template.MutableTemplatedNode
Expand Down Expand Up @@ -32,6 +33,7 @@ class Relation(
companion object {
const val START_PART = "START_PART"
const val END_PART = "END_PART"
const val LAYOUT = "LAYOUT"
}

@NodeRelationship(BaseTemplate.USED_IN, Direction.INCOMING)
Expand Down Expand Up @@ -73,4 +75,8 @@ class Relation(
@GraphQLDescription("InterfaceDefinition this Relation derives invisible")
@FilterProperty
val derivesInvisible by NodeSetProperty<InterfaceDefinition>()

@NodeRelationship(LAYOUT, Direction.OUTGOING)
@GraphQLIgnore
val layouts by NodeSetProperty<RelationLayout>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gropius.model.architecture

import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
import gropius.model.architecture.layout.RelationPartnerLayout
import gropius.model.issue.AggregatedIssue
import gropius.model.template.RelationPartnerTemplate
import gropius.model.template.TemplatedNode
Expand All @@ -21,6 +22,7 @@ abstract class RelationPartner(name: String, description: String) : AffectedByIs
const val INCOMING_RELATION = "INCOMING_RELATION"
const val OUTGOING_RELATION = "OUTGOING_RELATION"
const val AGGREGATED_ISSUE = "AGGREGATED_ISSUE"
const val LAYOUT = "LAYOUT"
}

@NodeRelationship(INCOMING_RELATION, Direction.OUTGOING)
Expand All @@ -38,6 +40,10 @@ abstract class RelationPartner(name: String, description: String) : AffectedByIs
@FilterProperty
val aggregatedIssues by NodeSetProperty<AggregatedIssue>()

@NodeRelationship(LAYOUT, Direction.OUTGOING)
@GraphQLIgnore
val layouts by NodeSetProperty<RelationPartnerLayout>()

/**
* Helper function to get the associated [RelationPartnerTemplate]
*
Expand Down
Loading

0 comments on commit 155fc9f

Please sign in to comment.