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

Proposal: convenience mutations for the creation of advanced tasks #3400

Open
bomoko opened this issue Feb 21, 2023 · 3 comments · May be fixed by #3401
Open

Proposal: convenience mutations for the creation of advanced tasks #3400

bomoko opened this issue Feb 21, 2023 · 3 comments · May be fixed by #3401

Comments

@bomoko
Copy link
Contributor

bomoko commented Feb 21, 2023

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/

Footnotes

  1. group level tasks should be deprecated immediately - there exists potential problems with namespace clashes. We should warn users not to use these and have “group level” functionality reintroduced with Organizations. 2

@shreddedbacon
Copy link
Member

shreddedbacon commented Jun 30, 2023

I'd like to also see some improvements to the task arguments to cater for additional cases

  1. There is no way to define an argument as optional, in the UI I have to populate all fields to be able to trigger the task. If a field is marked as optional, it could skip it.
  2. There is no way to define a default value, or values for an argument.
{
	name: "MY_OPTIONAL_VALUE"
	displayName: "Just a value that is optional"
	type: STRING
	optional: true
},
{
	name: "MY_VALUE"
	displayName: "Just a value"
	type: STRING
	default: "value"
}

Additionally to point 2, a type that could be STRING_RANGE, and then an input of stringRange: [string] so you could craft your own drop down of predefined options (could do NUMERIC_RANGE too?)

{
	name: "MY_RANGE_VALUE"
	displayName: "Just a value from a range"
	type: STRING_RANGE
	stringRange: ["value1", "value2"]
}

EDIT: I've raised #3477

@bomoko
Copy link
Contributor Author

bomoko commented Jul 2, 2023

Thanks for this, @shreddedbacon - I think that both of these suggestions can be factored into the current structure. Perhaps we spin this comment into another discussion/proposal. Should be easy to implement as well.

@shreddedbacon
Copy link
Member

If it can be done external to this proposal, I'll just create a feature request issue to get them added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants