add issue tasks.

This commit is contained in:
2020-05-10 20:50:18 +02:00
parent 3039dc1879
commit 47d8a027da
21 changed files with 598 additions and 15 deletions
+1
View File
@@ -5,6 +5,7 @@ package resolvers
import (
"context"
"git.aiterp.net/stufflog/server/graph/graphcore"
"git.aiterp.net/stufflog/server/models"
)
+9
View File
@@ -76,6 +76,15 @@ func (r *issueResolver) Status(ctx context.Context, obj *models.Issue) (*models.
return status, nil
}
func (r *issueResolver) Tasks(ctx context.Context, obj *models.Issue, filter *models.IssueTaskFilter) ([]*models.IssueTask, error) {
if filter == nil {
filter = &models.IssueTaskFilter{}
}
filter.IssueTaskIDs = []string{obj.ID}
return r.Database.IssueTasks().List(ctx, *filter)
}
// Issue returns graphcore.IssueResolver implementation.
func (r *Resolver) Issue() graphcore.IssueResolver { return &issueResolver{r} }
+92
View File
@@ -0,0 +1,92 @@
package resolvers
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"errors"
"fmt"
"strings"
"time"
"git.aiterp.net/stufflog/server/graph/graphcore"
"git.aiterp.net/stufflog/server/graph/graphutil"
"git.aiterp.net/stufflog/server/internal/xlerrors"
"git.aiterp.net/stufflog/server/models"
)
func (r *issueTaskResolver) EstimatedUnits(ctx context.Context, obj *models.IssueTask) (*int, error) {
// TODO: Data loader
activity, err := r.Database.Activities().Find(ctx, obj.ActivityID)
if err != nil {
return nil, err
}
if !activity.Countable || activity.UnitIsTimeSpent {
return nil, nil
}
return &obj.EstimatedUnits, nil
}
func (r *issueTaskResolver) Issue(ctx context.Context, obj *models.IssueTask) (*models.Issue, error) {
return r.Database.Issues().Find(ctx, obj.IssueID)
}
func (r *issueTaskResolver) Activity(ctx context.Context, obj *models.IssueTask) (*models.Activity, error) {
return r.Database.Activities().Find(ctx, obj.ActivityID)
}
func (r *issueTaskResolver) Status(ctx context.Context, obj *models.IssueTask) (*models.ProjectStatus, error) {
// The project ID is always the prefix before the first dash in the issue ID.
split := strings.SplitN(obj.IssueID, "-", 2)
if len(split) == 0 {
return nil, errors.New("invalid issue ID")
}
projectID := split[0]
// Shortcut: if description isn't needed, resolve this with issue's properties.
if !graphutil.SelectsAnyField(ctx, "description") {
return &models.ProjectStatus{
ProjectID: projectID,
Stage: obj.StatusStage,
Name: obj.StatusName,
Description: "FAKE",
}, nil
}
// Find it in the database. TODO: DataLoader
status, err := r.Database.ProjectStatuses().Find(ctx, projectID, obj.StatusName)
if xlerrors.IsNotFound(err) {
return &models.ProjectStatus{
ProjectID: projectID,
Stage: obj.StatusStage,
Name: obj.StatusName,
Description: "(Deleted or unknown status)",
}, nil
} else if err != nil {
return nil, err
}
// If the stage doesn't match, sneakily correct it for next time.
if status.Stage != obj.StatusStage {
updatedTask := *obj
updatedTask.StatusStage = status.Stage
_ = r.Database.IssueTasks().Save(ctx, updatedTask)
}
return status, nil
}
func (r *issueTaskResolver) RemainingTime(ctx context.Context, obj *models.IssueTask) (time.Duration, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *issueTaskResolver) RemainingUnits(ctx context.Context, obj *models.IssueTask) (*int, error) {
panic(fmt.Errorf("not implemented"))
}
// IssueTask returns graphcore.IssueTaskResolver implementation.
func (r *Resolver) IssueTask() graphcore.IssueTaskResolver { return &issueTaskResolver{r} }
type issueTaskResolver struct{ *Resolver }
+115
View File
@@ -6,6 +6,7 @@ package resolvers
import (
"context"
"errors"
"time"
"git.aiterp.net/stufflog/server/graph/graphcore"
"git.aiterp.net/stufflog/server/internal/xlerrors"
@@ -169,6 +170,120 @@ func (r *mutationResolver) CreateIssue(ctx context.Context, input graphcore.Issu
return issue, nil
}
func (r *mutationResolver) CreateIssueTask(ctx context.Context, input graphcore.IssueTaskCreateInput) (*models.IssueTask, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, xlerrors.PermissionDenied
}
issue, err := r.Database.Issues().Find(ctx, input.IssueID)
if err != nil {
return nil, err
}
if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
return nil, xlerrors.PermissionDenied
}
status, err := r.Database.ProjectStatuses().Find(ctx, issue.ProjectID, input.StatusName)
if err != nil {
return nil, err
}
activity, err := r.Database.Activities().Find(ctx, input.ActivityID)
if err != nil {
return nil, err
} else if activity.ProjectID != issue.ProjectID {
return nil, xlerrors.NotFound("Activity")
}
issueTask := &models.IssueTask{
IssueID: issue.ID,
ActivityID: activity.ID,
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
StatusStage: status.Stage,
StatusName: status.Name,
Name: input.Name,
Description: input.Description,
PointsMultiplier: 1.0,
}
if input.EstimatedUnits != nil && activity.Countable && !activity.UnitIsTimeSpent {
issueTask.EstimatedUnits = *input.EstimatedUnits
}
if input.PointsMultiplier != nil && *input.PointsMultiplier > 0 {
issueTask.PointsMultiplier = *input.PointsMultiplier
}
issueTask, err = r.Database.IssueTasks().Insert(ctx, *issueTask)
if err != nil {
return nil, err
}
return issueTask, nil
}
func (r *mutationResolver) EditIssueTask(ctx context.Context, input graphcore.IssueTaskEditInput) (*models.IssueTask, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, xlerrors.PermissionDenied
}
task, err := r.Database.IssueTasks().Find(ctx, input.IssueTaskID)
if err != nil {
return nil, err
}
issue, err := r.Database.Issues().Find(ctx, task.IssueID)
if err != nil {
return nil, err
}
if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
return nil, xlerrors.PermissionDenied
}
if input.SetName != nil {
task.Name = *input.SetName
}
if input.SetDescription != nil {
task.Description = *input.SetDescription
}
if input.SetDueTime != nil {
task.DueTime = input.SetDueTime
}
if input.SetEstimatedTime != nil {
task.EstimatedTime = *input.SetEstimatedTime
}
if input.SetEstimatedUnits != nil {
activity, err := r.Database.Activities().Find(ctx, task.ActivityID)
if err != nil {
return nil, err
}
if activity.Countable && !activity.UnitIsTimeSpent {
task.EstimatedUnits = *input.SetEstimatedUnits
}
}
if input.SetPointsMultiplier != nil && *input.SetPointsMultiplier > 0 {
task.PointsMultiplier = *input.SetPointsMultiplier
}
if input.SetStatusName != nil {
status, err := r.Database.ProjectStatuses().Find(ctx, issue.ProjectID, *input.SetStatusName)
if err != nil {
return nil, err
}
task.StatusName = status.Name
task.StatusStage = status.Stage
}
err = r.Database.IssueTasks().Save(ctx, *task)
if err != nil {
return nil, err
}
return task, nil
}
func (r *mutationResolver) LoginUser(ctx context.Context, input graphcore.UserLoginInput) (*models.User, error) {
return r.Auth.Login(ctx, input.Username, input.Password)
}