From de7af3b1dd90571a57ce33ad70eccdd91f5a7992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=A4dorf?= Date: Wed, 28 Jun 2023 15:13:39 +0200 Subject: [PATCH] Add activities list command --- cmd/list/activities.go | 46 ++++++++++ cmd/list/list.go | 1 + components/printer/activities.go | 59 +++++++++++++ components/resources/activities/functions.go | 1 + components/resources/users/filters.go | 28 +++++++ components/resources/users/functions.go | 32 +++++++ .../resources/work_packages/activities.go | 22 +++++ dtos/activity.go | 83 +++++++++++++++++++ dtos/long_text.go | 18 ++++ dtos/user.go | 42 ++++++++++ dtos/work_package.go | 16 ++-- go.sum | 4 - models/activity.go | 11 +++ models/long_text.go | 7 ++ models/user.go | 8 ++ 15 files changed, 364 insertions(+), 14 deletions(-) create mode 100644 cmd/list/activities.go create mode 100644 components/printer/activities.go create mode 100644 components/resources/activities/functions.go create mode 100644 components/resources/users/filters.go create mode 100644 components/resources/users/functions.go create mode 100644 components/resources/work_packages/activities.go create mode 100644 dtos/activity.go create mode 100644 dtos/long_text.go create mode 100644 dtos/user.go create mode 100644 models/activity.go create mode 100644 models/long_text.go create mode 100644 models/user.go diff --git a/cmd/list/activities.go b/cmd/list/activities.go new file mode 100644 index 0000000..dff9818 --- /dev/null +++ b/cmd/list/activities.go @@ -0,0 +1,46 @@ +package list + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/opf/openproject-cli/components/printer" + "github.com/opf/openproject-cli/components/resources/users" + "github.com/opf/openproject-cli/components/resources/work_packages" +) + +var activitiesCmd = &cobra.Command{ + Use: "activities [wpId]", + Aliases: []string{"ac"}, + Short: "Lists activities for work package", + Long: `Get a list of activities for a work package.`, + Run: listActivities, +} + +func listActivities(_ *cobra.Command, args []string) { + if len(args) != 1 { + printer.ErrorText(fmt.Sprintf("Expected 1 argument [wpId], but got %d", len(args))) + } + + wpId, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + printer.ErrorText(err.Error()) + } + activities, err := work_packages.Activities(wpId) + if err != nil { + printer.ErrorText(err.Error()) + } + + userIds := []uint64{} + for _, a := range activities { + if a.UserId > 0 { + userIds = append(userIds, a.UserId) + continue + } + } + + users := users.ByIds(userIds) + printer.Activities(activities, users) +} diff --git a/cmd/list/list.go b/cmd/list/list.go index 26ae4c5..149a23a 100644 --- a/cmd/list/list.go +++ b/cmd/list/list.go @@ -44,5 +44,6 @@ func init() { projectsCmd, notificationsCmd, workPackagesCmd, + activitiesCmd, ) } diff --git a/components/printer/activities.go b/components/printer/activities.go new file mode 100644 index 0000000..e3f0320 --- /dev/null +++ b/components/printer/activities.go @@ -0,0 +1,59 @@ +package printer + +import ( + "sort" + "strings" + + "github.com/opf/openproject-cli/models" +) + +func Activities(activities []*models.Activity, users []*models.User) { + for _, activity := range activities { + user := &models.User{ + Id: 0, + Name: "", + FirstName: "", + LastName: "", + } + if activity.UserId > 0 { + userIndex := sort.Search(len(users)-1, func(i int) bool { return users[i].Id == activity.UserId }) + user = users[userIndex] + } + printActivityHeadline(activity, user) + printActivityBody(activity) + println("") + } +} + +func printActivityHeadline(activity *models.Activity, user *models.User) { + var parts []string + + if len(user.Name) > 0 { + parts = append(parts, Green(user.Name)) + } + + parts = append(parts, Yellow(activity.UpdatedAt)) + + activePrinter.Println(strings.Join(parts, " ")) +} + +func printActivityBody(activity *models.Activity) { + var parts []string + + if len(activity.Comment) > 0 { + parts = append(parts, Yellow(activity.Comment)) + + if len(activity.Details) > 0 { + parts = append(parts, "---") + } + } + + var detailsParts []string + for _, detail := range activity.Details { + detailsParts = append(detailsParts, *detail) + } + + parts = append(parts, strings.Join(detailsParts, "\n")) + + activePrinter.Println(strings.Join(parts, "\n \n")) +} diff --git a/components/resources/activities/functions.go b/components/resources/activities/functions.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/components/resources/activities/functions.go @@ -0,0 +1 @@ +package main diff --git a/components/resources/users/filters.go b/components/resources/users/filters.go new file mode 100644 index 0000000..b03c65b --- /dev/null +++ b/components/resources/users/filters.go @@ -0,0 +1,28 @@ +package users + +import ( + "strconv" + + "github.com/opf/openproject-cli/components/requests" +) + +func IdFilter(ids []uint64) requests.Filter { + idsStr := make([]string, len(ids)) + for idx, id := range ids { + idsStr[idx] = strconv.FormatUint(id, 10) + } + + return requests.Filter{ + Operator: "=", + Name: "id", + Values: idsStr, + } +} + +func NameFilter(name string) requests.Filter { + return requests.Filter{ + Operator: "~", + Name: "name", + Values: []string{name}, + } +} diff --git a/components/resources/users/functions.go b/components/resources/users/functions.go new file mode 100644 index 0000000..9893451 --- /dev/null +++ b/components/resources/users/functions.go @@ -0,0 +1,32 @@ +package users + +import ( + "github.com/opf/openproject-cli/components/parser" + "github.com/opf/openproject-cli/components/printer" + "github.com/opf/openproject-cli/components/requests" + "github.com/opf/openproject-cli/dtos" + "github.com/opf/openproject-cli/models" +) + +const apiPath = "api/v3" +const usersPath = apiPath + "/principals" + +func ByIds(ids []uint64) []*models.User { + if len(ids) == 0 { + return []*models.User{} + } + var filters []requests.Filter + filters = append(filters, IdFilter(ids)) + + query := requests.NewQuery(filters) + + requestUrl := usersPath + + status, response := requests.Get(requestUrl, &query) + if !requests.IsSuccess(status) { + printer.ResponseError(status, response) + } + + userCollection := parser.Parse[dtos.UserCollectionDto](response) + return userCollection.Convert() +} diff --git a/components/resources/work_packages/activities.go b/components/resources/work_packages/activities.go new file mode 100644 index 0000000..7797eeb --- /dev/null +++ b/components/resources/work_packages/activities.go @@ -0,0 +1,22 @@ +package work_packages + +import ( + "path/filepath" + "strconv" + + "github.com/opf/openproject-cli/components/parser" + "github.com/opf/openproject-cli/components/printer" + "github.com/opf/openproject-cli/components/requests" + "github.com/opf/openproject-cli/dtos" + "github.com/opf/openproject-cli/models" +) + +func Activities(id uint64) (activites []*models.Activity, err error) { + status, response := requests.Get(filepath.Join(workPackagesPath, strconv.FormatUint(id, 10), "activities"), nil) + if !requests.IsSuccess(status) { + printer.ResponseError(status, response) + } + + activitiesDto := parser.Parse[dtos.ActivityCollectionDto](response) + return activitiesDto.Convert() +} diff --git a/dtos/activity.go b/dtos/activity.go new file mode 100644 index 0000000..b577de1 --- /dev/null +++ b/dtos/activity.go @@ -0,0 +1,83 @@ +package dtos + +import ( + "strconv" + "strings" + + "github.com/opf/openproject-cli/models" +) + +type ActivityDto struct { + Id uint64 `json:"id,omitempty"` + Comment *LongTextDto `json:"comment,omitempty"` + Details []*LongTextDto `json:"details,omitempty"` + Version uint64 `json:"version,omitempty"` + CreatedAt string `json:"createdAt,omitempty"` + UpdatedAt string `json:"updatedAt,omitempty"` + Links *activityLinksDto `json:"_links,omitempty"` +} + +type activityElements struct { + Elements []*ActivityDto `json:"elements"` +} + +type ActivityCollectionDto struct { + Embedded activityElements `json:"_embedded"` + Type string `json:"_type"` + Total uint64 `json:"total"` + Count uint64 `json:"count"` +} + +type activityLinksDto struct { + User *activityUserLinkDto `json:"user"` +} + +type activityUserLinkDto struct { + Href string `json:"href"` +} + +/////////////// MODEL CONVERSION /////////////// + +func (dto *ActivityDto) Convert() (activity *models.Activity, err error) { + var userId uint64 + if len(dto.Links.User.Href) > 0 { + userHrefParts := strings.Split(dto.Links.User.Href, "/") + userIdStr := userHrefParts[len(userHrefParts)-1] + userId, err = strconv.ParseUint(userIdStr, 10, 64) + if err != nil { + return nil, err + } + } + + return &models.Activity{ + Id: dto.Id, + Comment: dto.Comment.Raw, + Details: mapDetailsDto(dto.Details), + Version: dto.Version, + CreatedAt: dto.CreatedAt, + UpdatedAt: dto.UpdatedAt, + UserId: userId, + }, nil +} + +func (dto *ActivityCollectionDto) Convert() (act []*models.Activity, err error) { + var activities = make([]*models.Activity, len(dto.Embedded.Elements)) + + for idx, p := range dto.Embedded.Elements { + activities[idx], err = p.Convert() + if err != nil { + return nil, err + } + } + + return activities, nil +} + +func mapDetailsDto(detailsDto []*LongTextDto) []*string { + var details = make([]*string, len(detailsDto)) + for idx, d := range detailsDto { + details[idx] = &d.Convert().Raw + } + + return details +} diff --git a/dtos/long_text.go b/dtos/long_text.go new file mode 100644 index 0000000..8bd246f --- /dev/null +++ b/dtos/long_text.go @@ -0,0 +1,18 @@ +package dtos + +import "github.com/opf/openproject-cli/models" + +type LongTextDto struct { + Format string `json:"format"` + Raw string `json:"raw"` + Html string `json:"html"` +} + +// ///////////// MODEL CONVERSION /////////////// +func (dto *LongTextDto) Convert() *models.LongText { + return &models.LongText{ + Format: dto.Format, + Raw: dto.Raw, + Html: dto.Html, + } +} diff --git a/dtos/user.go b/dtos/user.go new file mode 100644 index 0000000..34c8dbf --- /dev/null +++ b/dtos/user.go @@ -0,0 +1,42 @@ +package dtos + +import "github.com/opf/openproject-cli/models" + +type UserDto struct { + Id uint64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + FirstName string `json:"firstName,omitempty"` + LastName string `json:"lastName,omitempty"` +} + +type userElements struct { + Elements []*UserDto `json:"elements"` +} + +type UserCollectionDto struct { + Embedded userElements `json:"_embedded"` + Type string `json:"_type"` + Total uint64 `json:"total"` + Count uint64 `json:"count"` +} + +/////////////// MODEL CONVERSION /////////////// + +func (dto *UserDto) Convert() *models.User { + return &models.User{ + Id: dto.Id, + Name: dto.Name, + FirstName: dto.FirstName, + LastName: dto.LastName, + } +} + +func (dto *UserCollectionDto) Convert() []*models.User { + var users = make([]*models.User, len(dto.Embedded.Elements)) + + for idx, p := range dto.Embedded.Elements { + users[idx] = p.Convert() + } + + return users +} diff --git a/dtos/work_package.go b/dtos/work_package.go index f92ae67..85046d6 100644 --- a/dtos/work_package.go +++ b/dtos/work_package.go @@ -15,17 +15,13 @@ type WorkPackageLinksDto struct { PrepareAttachment *LinkDto `json:"prepareAttachment,omitempty"` } -type workPackageDescription struct { - Raw string `json:"raw"` -} - type WorkPackageDto struct { - Id int64 `json:"id,omitempty"` - Subject string `json:"subject,omitempty"` - Links *WorkPackageLinksDto `json:"_links,omitempty"` - Description *workPackageDescription `json:"description,omitempty"` - Embedded *embeddedDto `json:"_embedded,omitempty"` - LockVersion int `json:"lockVersion"` + Id int64 `json:"id,omitempty"` + Subject string `json:"subject,omitempty"` + Links *WorkPackageLinksDto `json:"_links,omitempty"` + Description *LongTextDto `json:"description,omitempty"` + Embedded *embeddedDto `json:"_embedded,omitempty"` + LockVersion int `json:"lockVersion,omitempty"` } type embeddedDto struct { diff --git a/go.sum b/go.sum index 4dbfa66..f3366a9 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,10 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/models/activity.go b/models/activity.go new file mode 100644 index 0000000..4ac2b3a --- /dev/null +++ b/models/activity.go @@ -0,0 +1,11 @@ +package models + +type Activity struct { + Id uint64 + Comment string + Details []*string + Version uint64 + CreatedAt string + UpdatedAt string + UserId uint64 +} diff --git a/models/long_text.go b/models/long_text.go new file mode 100644 index 0000000..37e1278 --- /dev/null +++ b/models/long_text.go @@ -0,0 +1,7 @@ +package models + +type LongText struct { + Format string + Raw string + Html string +} diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..cc749ae --- /dev/null +++ b/models/user.go @@ -0,0 +1,8 @@ +package models + +type User struct { + Id uint64 + Name string + FirstName string + LastName string +}