Skip to content

Commit

Permalink
Add sub project filter to list workpackages
Browse files Browse the repository at this point in the history
  • Loading branch information
Kharonus committed May 27, 2024
1 parent 33624ab commit c7f42ca
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 78 deletions.
61 changes: 2 additions & 59 deletions cmd/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The list can get filtered further.`,
}

func init() {
initWorkPackagesFlags()

notificationsCmd.Flags().StringVarP(
&notificationReason,
"reason",
Expand All @@ -18,65 +20,6 @@ func init() {
"The reason for the notification",
)

workPackagesCmd.Flags().StringVarP(
&assignee,
"assignee",
"a",
"",
"Assignee of the work package (can be name, ID or 'me')",
)

workPackagesCmd.Flags().Uint64VarP(
&projectId,
"project-id",
"p",
0,
"Show only work packages within the specified projectId")

workPackagesCmd.Flags().StringVarP(
&version,
"version",
"v",
"",
"Show only work packages having the specified version")

workPackagesCmd.Flags().StringVarP(
&statusFilter,
"status",
"s",
"",
`Show only work packages having the specified status. The value can be the
keywords 'open', 'closed', a single ID or a comma separated array of IDs, i.e.
'7,13'. Multiple values are concatenated with a logical 'OR'. If the IDs are
prefixed with an '!' the list is instead filtered to not have the specified
status.`)

workPackagesCmd.Flags().StringVarP(
&typeFilter,
"type",
"t",
"",
`Show only work packages having the specified types. The value can be a single
ID or a comma separated array of IDs, i.e. '7,13'. Multiple values are
concatenated with a logical 'OR'. If the IDs are prefixed with an '!' the list
is instead filtered to not have the specified status.`)

workPackagesCmd.Flags().BoolVarP(
&includeSubProjects,
"include-sub-projects",
"",
false,
`If listing the work packages of a project, this flag indicates if work
packages of sub projects should be included in the list. If omitting the flag,
the default is false.`)

workPackagesCmd.Flags().BoolVarP(
&showTotal,
"total",
"",
false,
"Show only the total number of work packages matching the filter options.")

RootCmd.AddCommand(
projectsCmd,
notificationsCmd,
Expand Down
32 changes: 24 additions & 8 deletions cmd/list/work_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var showTotal bool
var statusFilter string
var typeFilter string
var includeSubProjects bool
var subProject string

var workPackagesCmd = &cobra.Command{
Use: "workpackages",
Expand All @@ -31,8 +32,8 @@ var workPackagesCmd = &cobra.Command{
}

func listWorkPackages(_ *cobra.Command, _ []string) {
if len(version) != 0 && projectId == 0 {
printer.ErrorText("Version flag (--version) can only be used in conjunction with projectId flag (-p or --project-id).")
if errorText := validateCommandFlags(); len(errorText) > 0 {
printer.ErrorText(errorText)
return
}

Expand All @@ -47,6 +48,17 @@ func listWorkPackages(_ *cobra.Command, _ []string) {
}
}

func validateCommandFlags() (errorText string) {
switch {
case len(version) != 0 && projectId == 0:
return "Version flag (--version) can only be used in conjunction with projectId flag (-p or --project-id)."
case len(subProject) > 0 && (!includeSubProjects || projectId == 0):
return "Sub project filter flag (--sub-project) can only be used in conjunction with setting the flag--include-sub-projects and setting a project with the projectId flag (-p or --project-id)."
default:
return ""
}
}

func filterOptions() *map[work_packages.FilterOption]string {
options := make(map[work_packages.FilterOption]string)

Expand All @@ -61,11 +73,15 @@ func filterOptions() *map[work_packages.FilterOption]string {
}

if len(statusFilter) > 0 {
options[work_packages.Status] = validateStatusFilterValue(statusFilter)
options[work_packages.Status] = validateFilterValue(work_packages.Status, statusFilter)
}

if len(typeFilter) > 0 {
options[work_packages.Type] = validateStatusFilterValue(typeFilter)
options[work_packages.Type] = validateFilterValue(work_packages.Type, typeFilter)
}

if len(subProject) > 0 {
options[work_packages.SubProject] = validateFilterValue(work_packages.SubProject, subProject)
}

if len(version) > 0 {
Expand Down Expand Up @@ -105,16 +121,16 @@ func validatedVersionId(version string) string {
return strconv.FormatUint(filteredVersions[0].Id, 10)
}

func validateStatusFilterValue(status string) string {
matched, err := regexp.Match(`^(open)$|^(closed)$|^(!?[0-9,]+)$`, []byte(status))
func validateFilterValue(filter work_packages.FilterOption, value string) string {
matched, err := regexp.Match(work_packages.InputValidationExpression[filter], []byte(value))
if err != nil {
printer.Error(err)
}

if !matched {
printer.ErrorText(fmt.Sprintf("Invalid status filter value %s.", printer.Yellow(status)))
printer.ErrorText(fmt.Sprintf("Invalid %s value %s.", filter, printer.Yellow(value)))
os.Exit(-1)
}

return status
return value
}
74 changes: 74 additions & 0 deletions cmd/list/work_packages_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package list

func initWorkPackagesFlags() {
workPackagesCmd.Flags().StringVarP(
&assignee,
"assignee",
"a",
"",
"Assignee of the work package (can be name, ID or 'me')",
)

workPackagesCmd.Flags().Uint64VarP(
&projectId,
"project-id",
"p",
0,
"Show only work packages within the specified projectId")

workPackagesCmd.Flags().StringVarP(
&version,
"version",
"v",
"",
"Show only work packages having the specified version")

workPackagesCmd.Flags().StringVarP(
&statusFilter,
"status",
"s",
"",
`Show only work packages having the specified status. The value can be the
keywords 'open', 'closed', a single ID or a comma separated array of IDs, i.e.
'7,13'. Multiple values are concatenated with a logical 'OR'. If the IDs are
prefixed with an '!' the list is instead filtered to not have the specified
status.`)

workPackagesCmd.Flags().StringVarP(
&typeFilter,
"type",
"t",
"",
`Show only work packages having the specified types. The value can be a single
ID or a comma separated array of IDs, i.e. '7,13'. Multiple values are
concatenated with a logical 'OR'. If the IDs are prefixed with an '!' the list
is instead filtered to not have the specified status.`)

workPackagesCmd.Flags().StringVarP(
&subProject,
"sub-project",
"",
"",
`Show only work packages of the specified subprojects. This filter only applies,
if the flag '--include-sub-projects' is set. It then includes only the sub
projects matching the filter. The value can be a single ID or a comma separated
array of IDs, i.e. '7,13'. Multiple values are concatenated with a logical
'OR'. If the IDs are prefixed with an '!' instead the specified sub projects
are excluded.`)

workPackagesCmd.Flags().BoolVarP(
&includeSubProjects,
"include-sub-projects",
"",
false,
`If listing the work packages of a project, this flag indicates if work
packages of sub projects should be included in the list. If omitting the flag,
the default is false.`)

workPackagesCmd.Flags().BoolVarP(
&showTotal,
"total",
"",
false,
"Show only the total number of work packages matching the filter options.")
}
59 changes: 59 additions & 0 deletions components/resources/work_packages/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,45 @@ import (
"github.com/opf/openproject-cli/components/requests"
)

type FilterOption int

const (
Assignee FilterOption = iota
Version
Project
Status
Type
SubProject
IncludeSubProjects
)

var InputValidationExpression = map[FilterOption]string{
Status: "^(open)$|^(closed)$|^(!?[0-9,]+)$",
Type: "^(!?[0-9,]+)$",
SubProject: "^(!?[0-9,]+)$",
}

func (f FilterOption) String() string {
switch f {
case Assignee:
return "assignee"
case Version:
return "version"
case Project:
return "project"
case Status:
return "status"
case Type:
return "type"
case SubProject:
return "sub-project"
case IncludeSubProjects:
return "include-sub-projects"
default:
return "filter"
}
}

func AssigneeFilter(name string) requests.Filter {
return requests.Filter{
Operator: "=",
Expand Down Expand Up @@ -42,6 +81,26 @@ func TypeFilter(workPackageType string) requests.Filter {
}
}

func SubProjectFilter(filterValue string) requests.Filter {
var operator string
var values []string

switch {
case strings.Index(filterValue, "!") == 0:
operator = "!"
values = strings.Split(filterValue[1:], ",")
default:
operator = "="
values = strings.Split(filterValue, ",")
}

return requests.Filter{
Operator: operator,
Name: "subprojectId",
Values: values,
}
}

func StatusFilter(status string) requests.Filter {
var operator string
var values []string
Expand Down
13 changes: 2 additions & 11 deletions components/resources/work_packages/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@ import (
"github.com/opf/openproject-cli/models"
)

type FilterOption int

const (
Assignee FilterOption = iota
Version
Project
Status
Type
IncludeSubProjects
)

func Lookup(id uint64) (*models.WorkPackage, error) {
workPackage, err := fetch(id)
if err != nil {
Expand All @@ -47,6 +36,8 @@ func All(filterOptions *map[FilterOption]string, showOnlyTotal bool) (*models.Wo
filters = append(filters, StatusFilter(value))
case Type:
filters = append(filters, TypeFilter(value))
case SubProject:
filters = append(filters, SubProjectFilter(value))
case Project:
n, _ := strconv.ParseUint(value, 10, 64)
projectId = &n
Expand Down

0 comments on commit c7f42ca

Please sign in to comment.