-
Notifications
You must be signed in to change notification settings - Fork 157
Description
Note: reference/POC implementation given in #3401
Motivation
The mutation for creating advanced tasks is presently quite difficult to understand.
Let’s take a look at the input type for the AddAdvancedTaskDefinition mutation:
input AdvancedTaskDefinitionInput {
name: String
description: String
image: String
type: AdvancedTaskDefinitionTypes
service: String
command: String
environment: Int
project: Int
groupName: String
permission: TaskPermission
advancedTaskDefinitionArguments: [AdvancedTaskDefinitionArgumentInput]
confirmationText: String
}
The following fields are fairly straightforward:
name - the Task’s name
description - a string describing what the task does
permission - the role required to run the task.
advancedTaskDefinitionArguments - the arguments that can be passed to / are required by the task
confirmationText - text that is optionally displayed in contexts like the UI before running the task
Complexity, primarily because of ambiguity, is introduced by two subsets of fields.
Firstly, there are the fields
- type - whether a custom task runs what we call internally a “standard” or “advanced” task, namely, whether we run a command in one of the existing services or spin up a new container in the targeted environment’s namespace with a specific image
- This field accepts the arguments “COMMAND” and “IMAGE” for standard and advanced tasks respectively.
- command - the command that will be run if the “type” is set to “COMMAND”
- image - the container image that will be instantiated if the “type” is set to “IMAGE”
The ambiguity in the API here is that if one chooses to create an IMAGE type, then the “image” field should be filled. Likewise, if one creates a COMMAND type, then the “command” field should be filled.
This is in no way obvious when looking at the mutation, which makes it possible to, say, specify an IMAGE and fill in the “command” field (or both the “command” and “image” fields)
Secondly, there are the fields
- environment
- project
- groupName1
These three fields specify the target (or range of targets) of the task. Either a group (where all projects in the group will inherit the task), a project (where all environments in the project will have access to the task), or an environment directly.
The ambiguity here is that it seems as though one can set any combination of targets (or none) - whereas only one of the three should be set.
Both of these ambiguities are manifested in the fact that the mutation’s backend, rather than the type itself, is responsible for validating the incoming data.
The aim, then, is to disambiguate the type itself in such a way that the use of the mutation(s) are made obvious, thus also reducing the number of constraint checks in the backend as well.
Proposed solution
As a first step, leaning on the api structure and types would go a long way to contraining the users’ degrees of freedom, and thereby eliminate the ambiguity of the current method of defining custom tasks.
One way of achieving this would be to structure the mutation api via namespacing (see reference) and the creation of two new mutations specifically for the two types of advanced tasks.
type CustomTaskMutations {
createImage(input: CreateImageInput!): AdvancedTaskDefinitionImage
createCommand(input: CreateCommandInput!): AdvancedTaskDefinitionCommand
}
The two new input types would be as follows
input CreateImageInput {
name: String!
description: String!
image: String!
permission: TaskPermission!
target: CustomTaskTargetInput!
advancedTaskDefinitionArguments: [AdvancedTaskDefinitionArgumentInput]
confirmationText: String
}
input CreateCommandInput {
name: String!
description: String!
service: String!
command: String!
permission: TaskPermission!
target: CustomTaskTargetInput!
advancedTaskDefinitionArguments: [AdvancedTaskDefinitionArgumentInput]
confirmationText: String
}
As can be seen above - these two remove all ambiguity about what is required to create an IMAGE or COMMAND type both by removing all extraneous fields for the type, but also by setting required fields as mandatory.
Finally, the ambiguity around specifying the target is fixed by introducing the following type
input CustomTaskTargetInput {
projectName: String!
environmentName: String
}
The user will create the new task with either project name (which is required, and thereby guarantees that there will be a target), or with project and environment name.
Note, further, that there is no groupName target, so this mutation will also remove the problems described in 1
Example call
mutation customCommandTask {
customTasks {
createCommand(input: {
name: "printEnv"
description: "prints out the environment variables"
command: "env"
service: "cli"
target: {
projectName: "high-cotton"
}
permission: GUEST
}){
id
name
description
command
}
}
}
References
https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern/
https://khalilstemmler.com/blogs/graphql/nested-graphql-resolvers/