Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
27 changes: 21 additions & 6 deletions cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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 {
Expand Down Expand Up @@ -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)",
)
}
21 changes: 18 additions & 3 deletions tmetric/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
Expand All @@ -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)
Expand Down
46 changes: 44 additions & 2 deletions tmetric/tmetric.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"`
Expand Down Expand Up @@ -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(
Expand Down