diff --git a/README.md b/README.md index a46c09f..983b71c 100644 --- a/README.md +++ b/README.md @@ -89,3 +89,6 @@ go run main.go export --template template.tmpl #### work for a specific time period By default, the script will work with the current calendar month, but the start and end date can be configured with the `--start` and `--end` flags. The date format is `YYYY-MM-DD`. + +#### filter by project +To get the time entries of a specific project, use the `--project` flag with the name of the project. This flag can be used multiple times to include multiple projects. diff --git a/cmd/export.go b/cmd/export.go index e0d61f9..97eb97e 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -19,19 +19,21 @@ package cmd import ( "fmt" - "github.com/JankariTech/OpenProjectTmetricIntegration/config" - "github.com/JankariTech/OpenProjectTmetricIntegration/openproject" - "github.com/JankariTech/OpenProjectTmetricIntegration/tmetric" - "github.com/Masterminds/sprig/v3" - "github.com/spf13/cobra" "os" "path/filepath" "strings" "text/template" "time" + + "github.com/JankariTech/OpenProjectTmetricIntegration/config" + "github.com/JankariTech/OpenProjectTmetricIntegration/openproject" + "github.com/JankariTech/OpenProjectTmetricIntegration/tmetric" + "github.com/Masterminds/sprig/v3" + "github.com/spf13/cobra" ) var arbitraryString []string +var projects []string var tmplFile string var exportCmd = &cobra.Command{ @@ -58,7 +60,13 @@ var exportCmd = &cobra.Command{ return arbitraryString[i] }, "DetailedReport": func(clientName string, tagName string, groupName string) tmetric.Report { - report, _ := tmetric.GetDetailedReport(config, tmetricUser, clientName, tagName, groupName, startDate, endDate) + report, err := tmetric.GetDetailedReport( + config, tmetricUser, clientName, tagName, groupName, startDate, endDate, projects, + ) + if err != nil { + _, _ = fmt.Fprint(os.Stderr, err) + os.Exit(1) + } return report }, "AllWorkTypes": func() []tmetric.Tag { @@ -133,4 +141,11 @@ func init() { exportCmd.MarkFlagRequired("arbitraryString") exportCmd.Flags().StringVarP(&tmplFile, "template", "t", today, "the template file") exportCmd.MarkFlagRequired("template") + exportCmd.Flags().StringArrayVarP( + &projects, + "project", + "p", + nil, + "name of the tmetric project to include in the report (can be specified multiple times)", + ) } diff --git a/tmetric/report.go b/tmetric/report.go index dc6875b..00d9403 100644 --- a/tmetric/report.go +++ b/tmetric/report.go @@ -3,11 +3,12 @@ package tmetric import ( "encoding/json" "fmt" - "github.com/JankariTech/OpenProjectTmetricIntegration/config" - "github.com/go-resty/resty/v2" "net/url" "strconv" "time" + + "github.com/JankariTech/OpenProjectTmetricIntegration/config" + "github.com/go-resty/resty/v2" ) type ReportItem struct { @@ -55,7 +56,7 @@ func (reportItem *ReportItem) getDuration() (time.Duration, error) { } func GetDetailedReport( - config *config.Config, tmetricUser User, clientName string, tagName string, groupName string, startDate string, endDate string, + config *config.Config, tmetricUser User, clientName string, tagName string, groupName string, startDate string, endDate string, projects []string, ) (Report, error) { client, err := getClientByName(config, tmetricUser, clientName) if err != nil { @@ -66,6 +67,17 @@ func GetDetailedReport( if err != nil { return Report{}, err } + + var projectsIds []string // we need a slice of strings for the URL parameters, so let's declare it a string slice + for _, projectName := range projects { + project, err := getProjectByName(config, tmetricUser, projectName) + if err != nil { + return Report{}, err + } + + projectsIds = append(projectsIds, strconv.Itoa(project.Id)) + } + httpClient := resty.New() tmetricUrl, _ := url.JoinPath(config.TmetricAPIBaseUrl, "reports/detailed") request := httpClient.R() @@ -87,6 +99,9 @@ func GetDetailedReport( SetQueryParam("AccountId", strconv.Itoa(tmetricUser.ActiveAccountId)). SetQueryParam("ClientList", strconv.Itoa(client.Id)). SetQueryParam("GroupList", strconv.Itoa(team.Id)). + SetQueryParamsFromValues(url.Values{ + "ProjectList": projectsIds, + }). SetQueryParam("StartDate", startDate). SetQueryParam("EndDate", endDate). Get(tmetricUrl) diff --git a/tmetric/tmetric.go b/tmetric/tmetric.go index 8490e5f..06df02b 100644 --- a/tmetric/tmetric.go +++ b/tmetric/tmetric.go @@ -3,12 +3,13 @@ package tmetric import ( "encoding/json" "fmt" - "github.com/JankariTech/OpenProjectTmetricIntegration/config" - "github.com/go-resty/resty/v2" "net/url" "sort" "strconv" "strings" + + "github.com/JankariTech/OpenProjectTmetricIntegration/config" + "github.com/go-resty/resty/v2" ) // ClientV2 represents a client that is returned by the Tmetric API V2 @@ -26,6 +27,13 @@ type TagV2 struct { IsWorkType bool `json:"isWorkType"` } +// ProjectV2 represents a project that is returned by the Tmetric API V2 +// see https://app.tmetric.com/api-docs/v2/#/ProjectsV2/projectsv2-get-api-accounts-accountid-projects +type ProjectV2 struct { + Id int `json:"projectId"` + Name string `json:"projectName"` +} + type Team struct { Name string `json:"name"` Id int `json:"id"` @@ -65,6 +73,40 @@ func getTeamByName(config *config.Config, tmetricUser User, name string) (Team, return Team{}, fmt.Errorf("could not find any team with name '%v'", name) } +func getAllProjects(config *config.Config, tmetricUser User) ([]ProjectV2, error) { + httpClient := resty.New() + tmetricUrl, _ := url.JoinPath( + config.TmetricAPIBaseUrl, "accounts/", strconv.Itoa(tmetricUser.ActiveAccountId), "/projects", + ) + resp, err := httpClient.R(). + SetAuthToken(config.TmetricToken). + Get(tmetricUrl) + if err != nil || resp.StatusCode() != 200 { + return nil, fmt.Errorf( + "cannot read projects from tmetric. Error: '%v'. HTTP status code: %v", err, resp.StatusCode(), + ) + } + var projects []ProjectV2 + err = json.Unmarshal(resp.Body(), &projects) + if err != nil { + return nil, fmt.Errorf("error parsing project response: %v\n", err) + } + return projects, nil +} + +func getProjectByName(config *config.Config, tmetricUser User, name string) (ProjectV2, error) { + projects, err := getAllProjects(config, tmetricUser) + if err != nil { + return ProjectV2{}, err + } + for _, project := range projects { + if project.Name == name { + return project, nil + } + } + return ProjectV2{}, fmt.Errorf("could not find any project in tmetric with name '%v'", name) +} + func GetAllWorkTypes(config *config.Config, tmetricUser User) ([]Tag, error) { httpClient := resty.New() tmetricUrl, _ := url.JoinPath(