graphql: change mutations to respect the Relay specification

https://facebook.github.io/relay/graphql/mutations.htm

This specification also allow to expose a mutationId for fire and forget,
as well as the created operation.
This commit is contained in:
Michael Muré 2019-06-16 21:29:49 +02:00
parent 08c0e18ade
commit b2f8572c44
10 changed files with 2669 additions and 316 deletions

View File

@ -41,7 +41,7 @@ func (c *MultiRepoCache) RegisterDefaultRepository(repo repository.ClockedRepo)
return nil
}
// ResolveRepo retrieve a repository by name
// DefaultRepo retrieve the default repository
func (c *MultiRepoCache) DefaultRepo() (*RepoCache, error) {
if len(c.repos) != 1 {
return nil, fmt.Errorf("repository is not unique")
@ -54,7 +54,7 @@ func (c *MultiRepoCache) DefaultRepo() (*RepoCache, error) {
panic("unreachable")
}
// DefaultRepo retrieve the default repository
// ResolveRepo retrieve a repository by name
func (c *MultiRepoCache) ResolveRepo(ref string) (*RepoCache, error) {
r, ok := c.repos[ref]
if !ok {

View File

@ -48,4 +48,6 @@ models:
SetStatusTimelineItem:
model: github.com/MichaelMure/git-bug/bug.SetStatusTimelineItem
SetTitleTimelineItem:
model: github.com/MichaelMure/git-bug/bug.SetTitleTimelineItem
model: github.com/MichaelMure/git-bug/bug.SetTitleTimelineItem
LabelChangeResult:
model: github.com/MichaelMure/git-bug/bug.LabelChangeResult

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/git"
)
// An object that has an author.
@ -16,6 +17,28 @@ type Authored interface {
IsAuthored()
}
type AddCommentInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The bug ID's prefix.
Prefix string `json:"prefix"`
// The first message of the new bug.
Message string `json:"message"`
// The collection of file's hash required for the first message.
Files []git.Hash `json:"files"`
}
type AddCommentPayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The affected bug.
Bug *bug.Snapshot `json:"bug"`
// The resulting operation.
Operation *bug.AddCommentOperation `json:"operation"`
}
// The connection type for Bug.
type BugConnection struct {
// A list of edges.
@ -35,6 +58,48 @@ type BugEdge struct {
Node *bug.Snapshot `json:"node"`
}
type ChangeLabelInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The bug ID's prefix.
Prefix string `json:"prefix"`
// The list of label to add.
Added []string `json:"added"`
// The list of label to remove.
Removed []string `json:"Removed"`
}
type ChangeLabelPayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The affected bug.
Bug *bug.Snapshot `json:"bug"`
// The resulting operation.
Operation *bug.LabelChangeOperation `json:"operation"`
// The effect each source label had.
Results []*bug.LabelChangeResult `json:"results"`
}
type CloseBugInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The bug ID's prefix.
Prefix string `json:"prefix"`
}
type CloseBugPayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The affected bug.
Bug *bug.Snapshot `json:"bug"`
// The resulting operation.
Operation *bug.SetStatusOperation `json:"operation"`
}
type CommentConnection struct {
Edges []*CommentEdge `json:"edges"`
Nodes []*bug.Comment `json:"nodes"`
@ -47,6 +112,38 @@ type CommentEdge struct {
Node *bug.Comment `json:"node"`
}
type CommitAsNeededInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The bug ID's prefix.
Prefix string `json:"prefix"`
}
type CommitAsNeededPayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The affected bug.
Bug *bug.Snapshot `json:"bug"`
}
type CommitInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The bug ID's prefix.
Prefix string `json:"prefix"`
}
type CommitPayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The affected bug.
Bug *bug.Snapshot `json:"bug"`
}
type IdentityConnection struct {
Edges []*IdentityEdge `json:"edges"`
Nodes []identity.Interface `json:"nodes"`
@ -59,6 +156,46 @@ type IdentityEdge struct {
Node identity.Interface `json:"node"`
}
type NewBugInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The title of the new bug.
Title string `json:"title"`
// The first message of the new bug.
Message string `json:"message"`
// The collection of file's hash required for the first message.
Files []git.Hash `json:"files"`
}
type NewBugPayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The created bug.
Bug *bug.Snapshot `json:"bug"`
// The resulting operation.
Operation *bug.CreateOperation `json:"operation"`
}
type OpenBugInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The bug ID's prefix.
Prefix string `json:"prefix"`
}
type OpenBugPayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The affected bug.
Bug *bug.Snapshot `json:"bug"`
// The resulting operation.
Operation *bug.SetStatusOperation `json:"operation"`
}
// The connection type for an Operation
type OperationConnection struct {
Edges []*OperationEdge `json:"edges"`
@ -85,6 +222,26 @@ type PageInfo struct {
EndCursor string `json:"endCursor"`
}
type SetTitleInput struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// "The name of the repository. If not set, the default repository is used.
RepoRef *string `json:"repoRef"`
// The bug ID's prefix.
Prefix string `json:"prefix"`
// The new title.
Title string `json:"title"`
}
type SetTitlePayload struct {
// A unique identifier for the client performing the mutation.
ClientMutationID *string `json:"clientMutationId"`
// The affected bug.
Bug *bug.Snapshot `json:"bug"`
// The resulting operation
Operation *bug.SetTitleOperation `json:"operation"`
}
// The connection type for TimelineItem
type TimelineItemConnection struct {
Edges []*TimelineItemEdge `json:"edges"`
@ -99,6 +256,53 @@ type TimelineItemEdge struct {
Node bug.TimelineItem `json:"node"`
}
type LabelChangeStatus string
const (
LabelChangeStatusAdded LabelChangeStatus = "ADDED"
LabelChangeStatusRemoved LabelChangeStatus = "REMOVED"
LabelChangeStatusDuplicateInOp LabelChangeStatus = "DUPLICATE_IN_OP"
LabelChangeStatusAlreadyExist LabelChangeStatus = "ALREADY_EXIST"
LabelChangeStatusDoesntExist LabelChangeStatus = "DOESNT_EXIST"
)
var AllLabelChangeStatus = []LabelChangeStatus{
LabelChangeStatusAdded,
LabelChangeStatusRemoved,
LabelChangeStatusDuplicateInOp,
LabelChangeStatusAlreadyExist,
LabelChangeStatusDoesntExist,
}
func (e LabelChangeStatus) IsValid() bool {
switch e {
case LabelChangeStatusAdded, LabelChangeStatusRemoved, LabelChangeStatusDuplicateInOp, LabelChangeStatusAlreadyExist, LabelChangeStatusDoesntExist:
return true
}
return false
}
func (e LabelChangeStatus) String() string {
return string(e)
}
func (e *LabelChangeStatus) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = LabelChangeStatus(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid LabelChangeStatus", str)
}
return nil
}
func (e LabelChangeStatus) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type Status string
const (

View File

@ -2,10 +2,12 @@ package resolvers
import (
"context"
"fmt"
"image/color"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.LabelResolver = &labelResolver{}
@ -20,3 +22,24 @@ func (labelResolver) Color(ctx context.Context, obj *bug.Label) (*color.RGBA, er
rgba := obj.RGBA()
return &rgba, nil
}
var _ graph.LabelChangeResultResolver = &labelChangeResultResolver{}
type labelChangeResultResolver struct{}
func (labelChangeResultResolver) Status(ctx context.Context, obj *bug.LabelChangeResult) (models.LabelChangeStatus, error) {
switch obj.Status {
case bug.LabelChangeAdded:
return models.LabelChangeStatusAdded, nil
case bug.LabelChangeRemoved:
return models.LabelChangeStatusRemoved, nil
case bug.LabelChangeDuplicateInOp:
return models.LabelChangeStatusDuplicateInOp, nil
case bug.LabelChangeAlreadySet:
return models.LabelChangeStatusAlreadyExist, nil
case bug.LabelChangeDoesntExist:
return models.LabelChangeStatusDoesntExist, nil
}
return "", fmt.Errorf("unknown status")
}

View File

@ -6,7 +6,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.MutationResolver = &mutationResolver{}
@ -23,27 +23,152 @@ func (r mutationResolver) getRepo(repoRef *string) (*cache.RepoCache, error) {
return r.cache.DefaultRepo()
}
func (r mutationResolver) NewBug(ctx context.Context, repoRef *string, title string, message string, files []git.Hash) (*bug.Snapshot, error) {
repo, err := r.getRepo(repoRef)
func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput) (*models.NewBugPayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.NewBugWithFiles(title, message, files)
b, op, err := repo.NewBugWithFiles(input.Title, input.Message, input.Files)
if err != nil {
return nil, err
}
return b.Snapshot(), nil
return &models.NewBugPayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
Operation: op,
}, nil
}
func (r mutationResolver) Commit(ctx context.Context, repoRef *string, prefix string) (*bug.Snapshot, error) {
repo, err := r.getRepo(repoRef)
func (r mutationResolver) AddComment(ctx context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(prefix)
b, err := repo.ResolveBugPrefix(input.Prefix)
if err != nil {
return nil, err
}
op, err := b.AddCommentWithFiles(input.Message, input.Files)
if err != nil {
return nil, err
}
return &models.AddCommentPayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
Operation: op,
}, nil
}
func (r mutationResolver) ChangeLabels(ctx context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(input.Prefix)
if err != nil {
return nil, err
}
results, op, err := b.ChangeLabels(input.Added, input.Removed)
if err != nil {
return nil, err
}
resultsPtr := make([]*bug.LabelChangeResult, len(results))
for i, result := range results {
resultsPtr[i] = &result
}
return &models.ChangeLabelPayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
Operation: op,
Results: resultsPtr,
}, nil
}
func (r mutationResolver) OpenBug(ctx context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(input.Prefix)
if err != nil {
return nil, err
}
op, err := b.Open()
if err != nil {
return nil, err
}
return &models.OpenBugPayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
Operation: op,
}, nil
}
func (r mutationResolver) CloseBug(ctx context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(input.Prefix)
if err != nil {
return nil, err
}
op, err := b.Close()
if err != nil {
return nil, err
}
return &models.CloseBugPayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
Operation: op,
}, nil
}
func (r mutationResolver) SetTitle(ctx context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(input.Prefix)
if err != nil {
return nil, err
}
op, err := b.SetTitle(input.Title)
if err != nil {
return nil, err
}
return &models.SetTitlePayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
Operation: op,
}, nil
}
func (r mutationResolver) Commit(ctx context.Context, input models.CommitInput) (*models.CommitPayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(input.Prefix)
if err != nil {
return nil, err
}
@ -53,100 +178,30 @@ func (r mutationResolver) Commit(ctx context.Context, repoRef *string, prefix st
return nil, err
}
return b.Snapshot(), nil
return &models.CommitPayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
}, nil
}
func (r mutationResolver) AddComment(ctx context.Context, repoRef *string, prefix string, message string, files []git.Hash) (*bug.Snapshot, error) {
repo, err := r.getRepo(repoRef)
func (r mutationResolver) CommitAsNeeded(ctx context.Context, input models.CommitAsNeededInput) (*models.CommitAsNeededPayload, error) {
repo, err := r.getRepo(input.RepoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(prefix)
b, err := repo.ResolveBugPrefix(input.Prefix)
if err != nil {
return nil, err
}
_, err = b.AddCommentWithFiles(message, files)
err = b.CommitAsNeeded()
if err != nil {
return nil, err
}
return b.Snapshot(), nil
}
func (r mutationResolver) ChangeLabels(ctx context.Context, repoRef *string, prefix string, added []string, removed []string) (*bug.Snapshot, error) {
repo, err := r.getRepo(repoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(prefix)
if err != nil {
return nil, err
}
_, _, err = b.ChangeLabels(added, removed)
if err != nil {
return nil, err
}
return b.Snapshot(), nil
}
func (r mutationResolver) Open(ctx context.Context, repoRef *string, prefix string) (*bug.Snapshot, error) {
repo, err := r.getRepo(repoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(prefix)
if err != nil {
return nil, err
}
_, err = b.Open()
if err != nil {
return nil, err
}
return b.Snapshot(), nil
}
func (r mutationResolver) Close(ctx context.Context, repoRef *string, prefix string) (*bug.Snapshot, error) {
repo, err := r.getRepo(repoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(prefix)
if err != nil {
return nil, err
}
_, err = b.Close()
if err != nil {
return nil, err
}
return b.Snapshot(), nil
}
func (r mutationResolver) SetTitle(ctx context.Context, repoRef *string, prefix string, title string) (*bug.Snapshot, error) {
repo, err := r.getRepo(repoRef)
if err != nil {
return nil, err
}
b, err := repo.ResolveBugPrefix(prefix)
if err != nil {
return nil, err
}
_, err = b.SetTitle(title)
if err != nil {
return nil, err
}
return b.Snapshot(), nil
return &models.CommitAsNeededPayload{
ClientMutationID: input.ClientMutationID,
Bug: b.Snapshot(),
}, nil
}

View File

@ -63,5 +63,5 @@ func convertStatus(status bug.Status) (models.Status, error) {
return models.StatusClosed, nil
}
return "", fmt.Errorf("Unknown status")
return "", fmt.Errorf("unknown status")
}

View File

@ -30,6 +30,10 @@ func (r RootResolver) Mutation() graph.MutationResolver {
}
}
func (RootResolver) Repository() graph.RepositoryResolver {
return &repoResolver{}
}
func (RootResolver) Bug() graph.BugResolver {
return &bugResolver{}
}
@ -86,10 +90,6 @@ func (RootResolver) LabelChangeOperation() graph.LabelChangeOperationResolver {
return &labelChangeOperation{}
}
func (RootResolver) Repository() graph.RepositoryResolver {
return &repoResolver{}
}
func (RootResolver) SetStatusOperation() graph.SetStatusOperationResolver {
return &setStatusOperationResolver{}
}
@ -97,3 +97,7 @@ func (RootResolver) SetStatusOperation() graph.SetStatusOperationResolver {
func (RootResolver) SetTitleOperation() graph.SetTitleOperationResolver {
return &setTitleOperationResolver{}
}
func (r RootResolver) LabelChangeResult() graph.LabelChangeResultResolver {
return &labelChangeResultResolver{}
}

View File

@ -0,0 +1,170 @@
input NewBugInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The title of the new bug."""
title: String!
"""The first message of the new bug."""
message: String!
"""The collection of file's hash required for the first message."""
files: [Hash!]
}
type NewBugPayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The created bug."""
bug: Bug!
"""The resulting operation."""
operation: CreateOperation!
}
input AddCommentInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The bug ID's prefix."""
prefix: String!
"""The first message of the new bug."""
message: String!
"""The collection of file's hash required for the first message."""
files: [Hash!]
}
type AddCommentPayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The affected bug."""
bug: Bug!
"""The resulting operation."""
operation: AddCommentOperation!
}
input ChangeLabelInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The bug ID's prefix."""
prefix: String!
"""The list of label to add."""
added: [String!]
"""The list of label to remove."""
Removed: [String!]
}
enum LabelChangeStatus {
ADDED
REMOVED
DUPLICATE_IN_OP
ALREADY_EXIST
DOESNT_EXIST
}
type LabelChangeResult {
"""The source label."""
label: Label!
"""The effect this label had."""
status: LabelChangeStatus!
}
type ChangeLabelPayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The affected bug."""
bug: Bug!
"""The resulting operation."""
operation: LabelChangeOperation!
"""The effect each source label had."""
results: [LabelChangeResult]!
}
input OpenBugInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The bug ID's prefix."""
prefix: String!
}
type OpenBugPayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The affected bug."""
bug: Bug!
"""The resulting operation."""
operation: SetStatusOperation!
}
input CloseBugInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The bug ID's prefix."""
prefix: String!
}
type CloseBugPayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The affected bug."""
bug: Bug!
"""The resulting operation."""
operation: SetStatusOperation!
}
input SetTitleInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The bug ID's prefix."""
prefix: String!
"""The new title."""
title: String!
}
type SetTitlePayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The affected bug."""
bug: Bug!
"""The resulting operation"""
operation: SetTitleOperation!
}
input CommitInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The bug ID's prefix."""
prefix: String!
}
type CommitPayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The affected bug."""
bug: Bug!
}
input CommitAsNeededInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
""""The name of the repository. If not set, the default repository is used."""
repoRef: String
"""The bug ID's prefix."""
prefix: String!
}
type CommitAsNeededPayload {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The affected bug."""
bug: Bug!
}

View File

@ -1,16 +1,25 @@
type Query {
"""The default unnamend repository."""
defaultRepository: Repository
repository(id: String!): Repository
"""Access a repository by reference/name."""
repository(ref: String!): Repository
}
type Mutation {
newBug(repoRef: String, title: String!, message: String!, files: [Hash!]): Bug!
addComment(repoRef: String, prefix: String!, message: String!, files: [Hash!]): Bug!
changeLabels(repoRef: String, prefix: String!, added: [String!], removed: [String!]): Bug!
open(repoRef: String, prefix: String!): Bug!
close(repoRef: String, prefix: String!): Bug!
setTitle(repoRef: String, prefix: String!, title: String!): Bug!
commit(repoRef: String, prefix: String!): Bug!
"""create a new bug"""
newBug(input: NewBugInput!): NewBugPayload!
"""Add a new comment to a bug"""
addComment(input: AddCommentInput!): AddCommentPayload!
"""Add or remove a set of label on a bug"""
changeLabels(input: ChangeLabelInput): ChangeLabelPayload!
"""Change a bug's status to open"""
openBug(input: OpenBugInput!): OpenBugPayload!
"""Change a bug's status to closed"""
closeBug(input: CloseBugInput!): CloseBugPayload!
"""Change a bug's titlel"""
setTitle(input: SetTitleInput!): SetTitlePayload!
"""Commit write the pending operations into storage. This mutation fail if nothing is pending"""
commit(input: CommitInput!): CommitPayload!
"""Commit write the pending operations into storage. This mutation succed if nothing is pending"""
commitAsNeeded(input: CommitAsNeededInput!): CommitAsNeededPayload!
}