Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,15 @@ projects -- Lists projects
workpackages -- Lists work packages

# Discover flags: hitting completion key after
op update workpackge 42 -
op update workpackage 42 -
# returns
--action -a -- Executes a custom action on a work package
--assignee -- Assign a user to the work package
--attach -- Attach a file to the work package
--description -- Change the work package description
--description-file -- Read the work package description from a file
--help -h -- help for workpackage
--parent -- Move the work package below another work package
--subject -- Change the subject of the work package
--type -t -- Change the work package type
```
Expand All @@ -146,10 +149,16 @@ of examples, that might be useful for a great number of people.
# Creating a work package in a project only by subject.
# Work package is created with many default values (as for type and status),
# very similar to how a work package is created inline in a work package table.
op create workpackge --project 11 'Document new CLI tool'
op create workpackage --project 11 'Document new CLI tool'

# Same command with shorthands and directly open it in a browser to continue working on it.
op create workpackge -p11 'Document new CLI tool' -o
op create workpackage -p11 'Document new CLI tool' -o

# Create a feature below an epic.
op create workpackage -p11 --type Feature --parent 42 'Build export flow'

# Create a work package with a markdown description from a file.
op create workpackage -p11 --description-file ./description.md 'Document new CLI tool'
```

#### Listing
Expand All @@ -170,7 +179,13 @@ op update workpackage 42 --action Claim

# Batch updating some properties of a work package
# Valid input will get processed, while invalid (e.g. wrongly typed) input will get omitted
op update workpackage 42 --subject 'The new subject' --status 'In Progress' --type Implementation
op update workpackage 42 --subject 'The new subject' --type Implementation

# Move a work package below another work package
op update workpackage 42 --parent 12

# Updating the description of a work package
op update workpackage 42 --description-file ./description.md

# Uploading an attachment to a work package
op update workpackage 42 --attach ./Downloads/Report.pdf
Expand Down
22 changes: 22 additions & 0 deletions cmd/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,27 @@ func init() {
"Change the work package type",
)

createWorkPackageCmd.Flags().StringVar(
&descriptionFlag,
"description",
"",
"Set the work package description",
)

createWorkPackageCmd.Flags().StringVar(
&descriptionFileFlag,
"description-file",
"",
"Read the work package description from a file",
)
createWorkPackageCmd.MarkFlagsMutuallyExclusive("description", "description-file")

createWorkPackageCmd.Flags().Uint64Var(
&parentFlag,
"parent",
0,
"Create the work package below another work package",
)

RootCmd.AddCommand(createWorkPackageCmd)
}
13 changes: 13 additions & 0 deletions cmd/create/work_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package create

import (
"fmt"
"strconv"

"github.com/spf13/cobra"

Expand All @@ -14,6 +15,9 @@ import (
var projectId uint64
var shouldOpenWorkPackageInBrowser bool
var typeFlag string
var descriptionFlag string
var descriptionFileFlag string
var parentFlag uint64

var createWorkPackageCmd = &cobra.Command{
Use: "workpackage [subject]",
Expand Down Expand Up @@ -53,6 +57,15 @@ func createOptions(subject string) map[work_packages.CreateOption]string {
if len(typeFlag) > 0 {
options[work_packages.CreateType] = typeFlag
}
if len(descriptionFlag) > 0 {
options[work_packages.CreateDescription] = descriptionFlag
}
if len(descriptionFileFlag) > 0 {
options[work_packages.CreateDescriptionFile] = descriptionFileFlag
}
if parentFlag > 0 {
options[work_packages.CreateParent] = strconv.FormatUint(parentFlag, 10)
}

return options
}
19 changes: 19 additions & 0 deletions cmd/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,23 @@ func addWorkPackageFlags() {
"",
"Change the work package type",
)
workPackageCmd.Flags().StringVar(
&descriptionFlag,
"description",
"",
"Change the work package description",
)
workPackageCmd.Flags().StringVar(
&descriptionFileFlag,
"description-file",
"",
"Read the work package description from a file",
)
workPackageCmd.MarkFlagsMutuallyExclusive("description", "description-file")
workPackageCmd.Flags().Uint64Var(
&parentFlag,
"parent",
0,
"Move the work package below another work package",
)
}
22 changes: 17 additions & 5 deletions cmd/update/work_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import (
)

var (
actionFlag string
assigneeFlag uint64
attachFlag string
subjectFlag string
typeFlag string
actionFlag string
assigneeFlag uint64
attachFlag string
subjectFlag string
typeFlag string
descriptionFlag string
descriptionFileFlag string
parentFlag uint64
)

var workPackageCmd = &cobra.Command{
Expand Down Expand Up @@ -63,6 +66,15 @@ func updateOptions() map[work_packages.UpdateOption]string {
if len(typeFlag) > 0 {
options[work_packages.UpdateType] = typeFlag
}
if len(descriptionFlag) > 0 {
options[work_packages.UpdateDescription] = descriptionFlag
}
if len(descriptionFileFlag) > 0 {
options[work_packages.UpdateDescriptionFile] = descriptionFileFlag
}
if parentFlag > 0 {
options[work_packages.UpdateParent] = strconv.FormatUint(parentFlag, 10)
}

return options
}
40 changes: 38 additions & 2 deletions components/resources/work_packages/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ type CreateOption int
const (
CreateSubject CreateOption = iota
CreateType
CreateDescription
CreateDescriptionFile
CreateParent
)

var createMap = map[CreateOption]func(projectId uint64, workPackage *dtos.WorkPackageDto, input string) error{
CreateSubject: subjectCreate,
CreateType: typeCreate,
CreateSubject: subjectCreate,
CreateType: typeCreate,
CreateDescription: descriptionCreate,
CreateDescriptionFile: descriptionFileCreate,
CreateParent: parentCreate,
}

func subjectCreate(_ uint64, workPackage *dtos.WorkPackageDto, input string) error {
Expand Down Expand Up @@ -60,6 +66,36 @@ func typeCreate(projectId uint64, workPackage *dtos.WorkPackageDto, input string
return nil
}

func descriptionCreate(_ uint64, workPackage *dtos.WorkPackageDto, input string) error {
workPackage.Description = description(input)

return nil
}

func descriptionFileCreate(_ uint64, workPackage *dtos.WorkPackageDto, input string) error {
description, err := descriptionFromFile(input)
if err != nil {
return err
}

workPackage.Description = description
return nil
}

func parentCreate(_ uint64, workPackage *dtos.WorkPackageDto, input string) error {
parent, err := parentLink(input)
if err != nil {
return err
}

if workPackage.Links == nil {
workPackage.Links = &dtos.WorkPackageLinksDto{}
}

workPackage.Links.Parent = parent
return nil
}

func Create(projectId uint64, options map[CreateOption]string) (*models.WorkPackage, error) {
return create(projectId, options)
}
Expand Down
31 changes: 31 additions & 0 deletions components/resources/work_packages/description.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package work_packages

import (
"os"
"strconv"

"github.com/opf/openproject-cli/components/paths"
"github.com/opf/openproject-cli/dtos"
)

func description(input string) *dtos.LongTextDto {
return &dtos.LongTextDto{Format: "markdown", Raw: input}
}

func descriptionFromFile(path string) (*dtos.LongTextDto, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}

return description(string(data)), nil
}

func parentLink(input string) (*dtos.LinkDto, error) {
parentId, err := strconv.ParseUint(input, 10, 64)
if err != nil {
return nil, err
}

return &dtos.LinkDto{Href: paths.WorkPackage(parentId)}, nil
}
43 changes: 39 additions & 4 deletions components/resources/work_packages/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,20 @@ const (
UpdateAttachment
UpdateSubject
UpdateType
UpdateDescription
UpdateDescriptionFile
UpdateParent
)

var patchableUpdates = []UpdateOption{UpdateSubject, UpdateType, UpdateAssignee}
var patchableUpdates = []UpdateOption{UpdateSubject, UpdateType, UpdateAssignee, UpdateDescription, UpdateDescriptionFile, UpdateParent}

var patchMap = map[UpdateOption]func(patch, workPackage *dtos.WorkPackageDto, input string) (string, error){
UpdateAssignee: assigneePatch,
UpdateType: typePatch,
UpdateSubject: subjectPatch,
UpdateAssignee: assigneePatch,
UpdateType: typePatch,
UpdateSubject: subjectPatch,
UpdateDescription: descriptionPatch,
UpdateDescriptionFile: descriptionFilePatch,
UpdateParent: parentPatch,
}

func Update(id uint64, options map[UpdateOption]string) (*models.WorkPackage, error) {
Expand Down Expand Up @@ -150,6 +156,35 @@ func subjectPatch(patch, _ *dtos.WorkPackageDto, input string) (string, error) {
return fmt.Sprintf("Subject -> %s", input), nil
}

func descriptionPatch(patch, _ *dtos.WorkPackageDto, input string) (string, error) {
patch.Description = description(input)
return "Description -> updated", nil
}

func descriptionFilePatch(patch, _ *dtos.WorkPackageDto, input string) (string, error) {
description, err := descriptionFromFile(input)
if err != nil {
return "", err
}

patch.Description = description
return fmt.Sprintf("Description -> %s", input), nil
}

func parentPatch(patch, _ *dtos.WorkPackageDto, input string) (string, error) {
parent, err := parentLink(input)
if err != nil {
return "", err
}

if patch.Links == nil {
patch.Links = &dtos.WorkPackageLinksDto{}
}

patch.Links.Parent = parent
return fmt.Sprintf("Parent -> #%s", input), nil
}

func assigneePatch(patch, _ *dtos.WorkPackageDto, input string) (string, error) {
userId, _ := strconv.ParseUint(input, 10, 64)

Expand Down
2 changes: 1 addition & 1 deletion dtos/long_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "github.com/opf/openproject-cli/models"
type LongTextDto struct {
Format string `json:"format"`
Raw string `json:"raw"`
Html string `json:"html"`
Html string `json:"html,omitempty"`
}

// ///////////// MODEL CONVERSION ///////////////
Expand Down
10 changes: 8 additions & 2 deletions dtos/work_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type WorkPackageLinksDto struct {
AddAttachment *LinkDto `json:"addAttachment,omitempty"`
Status *LinkDto `json:"status,omitempty"`
Project *LinkDto `json:"project,omitempty"`
Parent *LinkDto `json:"parent,omitempty"`
Assignee *LinkDto `json:"assignee,omitempty"`
Type *LinkDto `json:"type,omitempty"`
CustomActions []*LinkDto `json:"customActions,omitempty"`
Expand All @@ -21,7 +22,7 @@ type WorkPackageDto struct {
Links *WorkPackageLinksDto `json:"_links,omitempty"`
Description *LongTextDto `json:"description,omitempty"`
Embedded *embeddedDto `json:"_embedded,omitempty"`
LockVersion int `json:"lockVersion,omitempty"`
LockVersion int `json:"lockVersion"`
}

type embeddedDto struct {
Expand All @@ -48,13 +49,18 @@ type CreateWorkPackageDto struct {
/////////////// MODEL CONVERSION ///////////////

func (dto *WorkPackageDto) Convert() *models.WorkPackage {
description := ""
if dto.Description != nil {
description = dto.Description.Raw
}

return &models.WorkPackage{
Id: uint64(dto.Id),
Subject: dto.Subject,
Type: dto.Links.Type.Title,
Assignee: dto.Links.Assignee.Title,
Status: dto.Links.Status.Title,
Description: dto.Description.Raw,
Description: description,
LockVersion: dto.LockVersion,
}
}
Expand Down