From d85996c351099e702445cabe4c5e74c0b36887be Mon Sep 17 00:00:00 2001 From: Artur Neumann Date: Wed, 4 Feb 2026 15:00:28 +0545 Subject: [PATCH 1/4] plausability checks for the 'check' command --- cmd/tmetric.go | 62 +++++++++++++++++++++++++++++++++----- config/config.go | 8 +++-- openproject/workpackage.go | 18 +++++++++-- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/cmd/tmetric.go b/cmd/tmetric.go index cbf0959..65a91ab 100644 --- a/cmd/tmetric.go +++ b/cmd/tmetric.go @@ -20,15 +20,16 @@ package cmd import ( "errors" "fmt" + "os" + "strconv" + "strings" + "time" + "github.com/JankariTech/OpenProjectTmetricIntegration/config" "github.com/JankariTech/OpenProjectTmetricIntegration/openproject" "github.com/JankariTech/OpenProjectTmetricIntegration/tmetric" "github.com/manifoldco/promptui" "github.com/spf13/cobra" - "os" - "strconv" - "strings" - "time" ) func validateOpenProjectWorkPackage(input string) error { @@ -51,7 +52,7 @@ func handleEntriesWithoutIssue(timeEntries []tmetric.TimeEntry, tmetricUser tmet defer spinner.Stop() for _, entry := range entriesWithoutLinkToOpenProject { - prompt := promptui.Prompt{ + getWPPrompt := promptui.Prompt{ Label: fmt.Sprintf( "%v => %v %v-%v. Provide a WP number to be assigned to this time-entry (Enter to skip)", entry.Project.Name, entry.Note, entry.StartTime, entry.EndTime, @@ -62,7 +63,7 @@ func handleEntriesWithoutIssue(timeEntries []tmetric.TimeEntry, tmetricUser tmet workpackageFoundOnOpenProject := false for !workpackageFoundOnOpenProject { - workPackageId, err := prompt.Run() + workPackageId, err := getWPPrompt.Run() if err != nil { return fmt.Errorf("prompt failed: %v", err) @@ -82,7 +83,37 @@ func handleEntriesWithoutIssue(timeEntries []tmetric.TimeEntry, tmetricUser tmet continue } - prompt = promptui.Prompt{ + if !implausibleProjectConfirmation( + workPackage, + workPackage.Embedded.Project.Active, + "This project is NOT active! ", + "Do you want to use a WP from an INACTIVE project?", + ) { + workpackageFoundOnOpenProject = false + continue + } + + if !implausibleProjectConfirmation( + workPackage, + workPackage.Embedded.Project.Favorited, + "This project is none of your favorite projects!", + "Do you really want to use a WP from a not-favorite project?", + ) { + workpackageFoundOnOpenProject = false + continue + } + + if !implausibleProjectConfirmation( + workPackage, + workPackage.Embedded.Assignee.Name == tmetricUser.Name || workPackage.Embedded.Assignee.Name == config.OpenProjectTeam, + fmt.Sprintf("This WP is not assigned to you but to '%s'!", workPackage.Embedded.Assignee.Name), + "Do you really want to use a WP that is not assigned to you?", + ) { + workpackageFoundOnOpenProject = false + continue + } + + prompt := promptui.Prompt{ Label: fmt.Sprintf( "WP: %v. Subject: %v. Update t-metric entry?", workPackage.Id, workPackage.Subject, ), @@ -112,6 +143,23 @@ func handleEntriesWithoutIssue(timeEntries []tmetric.TimeEntry, tmetricUser tmet return nil } +func implausibleProjectConfirmation( + workPackage openproject.WorkPackage, condition bool, issue string, question string, +) bool { + if condition { + return true + } + prompt := promptui.Prompt{ + Label: fmt.Sprintf( + "⚠️ Found WP '%s' in the project '%s'. %s %s", + workPackage.Subject, workPackage.Embedded.Project.Name, issue, question, + ), + IsConfirm: true, + } + result, err := prompt.Run() + return err == nil && result == "y" +} + func handleEntriesWithoutWorkType(timeEntries []tmetric.TimeEntry, tmetricUser tmetric.User, config *config.Config) error { entriesWithoutWorkType := tmetric.GetEntriesWithoutWorkType(timeEntries) if len(entriesWithoutWorkType) > 0 { diff --git a/config/config.go b/config/config.go index f5653ca..a28a8aa 100644 --- a/config/config.go +++ b/config/config.go @@ -19,13 +19,15 @@ package config import ( "fmt" - "github.com/spf13/viper" "os" + + "github.com/spf13/viper" ) type Config struct { OpenProjectUrl string OpenProjectToken string + OpenProjectTeam string TmetricToken string ClientIdInTmetric int TmetricAPIBaseUrl string @@ -46,6 +48,7 @@ func NewConfig() *Config { fmt.Fprintln(os.Stderr, "openproject.token not set") os.Exit(1) } + openProjectTeam := viper.GetString("openproject.team") tmetricToken := viper.GetString("tmetric.token") if tmetricToken == "" { fmt.Fprintln(os.Stderr, "tmetric.token not set") @@ -64,6 +67,7 @@ func NewConfig() *Config { return &Config{ OpenProjectUrl: openProjectUrl, OpenProjectToken: openProjectToken, + OpenProjectTeam: openProjectTeam, TmetricToken: tmetricToken, ClientIdInTmetric: clientIdInTmetric, TmetricAPIBaseUrl: "https://app.tmetric.com/api/", @@ -72,6 +76,6 @@ func NewConfig() *Config { TmetricTagTransferredToOpenProject: "transferred-to-openproject", // this value has always to be "https://community.openproject.org" // otherwise tmetric does not recognize the integration and does not allow to create the external task - TmetricExternalTaskLink: "https://community.openproject.org/", + TmetricExternalTaskLink: "https://community.openproject.org/", } } diff --git a/openproject/workpackage.go b/openproject/workpackage.go index 0e5c08b..bebe493 100644 --- a/openproject/workpackage.go +++ b/openproject/workpackage.go @@ -20,15 +20,27 @@ package openproject import ( "encoding/json" "fmt" + "net/url" + "github.com/JankariTech/OpenProjectTmetricIntegration/config" "github.com/go-resty/resty/v2" "github.com/tidwall/gjson" - "net/url" ) type WorkPackage struct { - Subject string `json:"subject"` - Id int `json:"id"` + Subject string `json:"subject"` + Id int `json:"id"` + Embedded struct { + Project struct { + Id int `json:"id"` + Name string `json:"name"` + Active bool `json:"active"` + Favorited bool `json:"favorited"` + } `json:"project"` + Assignee struct { + Name string `json:"name"` + } `json:"assignee"` + } `json:"_embedded"` } func NewWorkPackage(id int, subject string) WorkPackage { From 89a19dc8e391c3037806e7443b00949bfa972ced Mon Sep 17 00:00:00 2001 From: Artur Neumann Date: Wed, 4 Feb 2026 15:38:33 +0545 Subject: [PATCH 2/4] show plausibility warnings in diff --- cmd/diff.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/cmd/diff.go b/cmd/diff.go index e1b607c..c55806f 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -41,6 +41,7 @@ type tableRow struct { OpenProjectEntry string OpenProjectDuration string DiffInTime string + Warnings string } var widthOfFixedColumns = 45 // rough size of all columns that have a fixed width @@ -119,7 +120,7 @@ var diffCmd = &cobra.Command{ outputTable.SetOutputMirror(os.Stdout) outputTable.AppendHeader( - table.Row{"date", "tmetric entry", "tm\ndur", "OpenProject entry", "OP\ndur", "time\ndiff"}, + table.Row{"date", "tmetric entry", "tm\ndur", "OpenProject entry", "OP\ndur", "time\ndiff", "warnings"}, ) widthContentColumns := int((getTerminalWidth() - widthOfFixedColumns) / 2) outputTable.SetColumnConfigs([]table.ColumnConfig{ @@ -177,7 +178,29 @@ var diffCmd = &cobra.Command{ sumDurationOpenProject += int(duration.Minutes()) humanReadableDuration, _ := entry.GetHumanReadableDuration() row.OpenProjectDuration += fmt.Sprintf("%v\n\n\n\n\n\n", humanReadableDuration) + workPackage, _ := openproject.GetWorkpackage( + path.Base(entry.Links.WorkPackage.Href), config, + ) + countWarnings := 0 + if !workPackage.Embedded.Project.Active { + row.Warnings += text.Snip("- inactive project\n", widthContentColumns, "~") + countWarnings++ + } + if !workPackage.Embedded.Project.Favorited { + row.Warnings += text.Snip("- not favorite project\n", widthContentColumns, "~") + countWarnings++ + } + if workPackage.Embedded.Assignee.Name != tmetricUser.Name && workPackage.Embedded.Assignee.Name != config.OpenProjectTeam { + row.Warnings += text.Snip("- not my assignment\n", widthContentColumns, "~") + countWarnings++ + } + // Add the remaining newlines to make it 6 rows total + for i := countWarnings; i < 6; i++ { + row.Warnings += "\n" + } + } + } if sumDurationTmetric > sumDurationOpenProject { diff := sumDurationTmetric - sumDurationOpenProject @@ -188,7 +211,6 @@ var diffCmd = &cobra.Command{ row.DiffInTime = strconv.Itoa(diff) totalTimeDiff += diff } - outputTable.AppendRow(table.Row{ row.Date, strings.Trim(row.TmetricEntry, "\n"), @@ -196,6 +218,7 @@ var diffCmd = &cobra.Command{ strings.Trim(row.OpenProjectEntry, "\n"), strings.Trim(row.OpenProjectDuration, "\n"), row.DiffInTime, + row.Warnings, }) outputTable.AppendSeparator() } From 20eeeee5dd9b34cab90beb032387366ee7756fe5 Mon Sep 17 00:00:00 2001 From: Artur Neumann Date: Thu, 5 Feb 2026 12:52:40 +0545 Subject: [PATCH 3/4] improve output, so that the table doesn't overflow --- cmd/diff.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/diff.go b/cmd/diff.go index c55806f..a4f5474 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -44,7 +44,7 @@ type tableRow struct { Warnings string } -var widthOfFixedColumns = 45 // rough size of all columns that have a fixed width +var widthOfFixedColumns = 61 // rough combined size of all columns that have a fixed width var userNameFromCmd string // tries to find out the width of the terminal and returns 80 if it fails @@ -183,17 +183,18 @@ var diffCmd = &cobra.Command{ ) countWarnings := 0 if !workPackage.Embedded.Project.Active { - row.Warnings += text.Snip("- inactive project\n", widthContentColumns, "~") - countWarnings++ + row.Warnings += "- inactive\n project\n" + countWarnings = countWarnings + 2 } if !workPackage.Embedded.Project.Favorited { - row.Warnings += text.Snip("- not favorite project\n", widthContentColumns, "~") - countWarnings++ + row.Warnings += "- not favorite\n project\n" + countWarnings = countWarnings + 2 } if workPackage.Embedded.Assignee.Name != tmetricUser.Name && workPackage.Embedded.Assignee.Name != config.OpenProjectTeam { - row.Warnings += text.Snip("- not my assignment\n", widthContentColumns, "~") - countWarnings++ + row.Warnings += "- not my\n assignment\n" + countWarnings = countWarnings + 2 } + // Add the remaining newlines to make it 6 rows total for i := countWarnings; i < 6; i++ { row.Warnings += "\n" From 6509845656014a9ffa08494ad4d6f7afdc44ddd1 Mon Sep 17 00:00:00 2001 From: Artur Neumann Date: Thu, 5 Feb 2026 13:21:10 +0545 Subject: [PATCH 4/4] show progress generating diff table --- cmd/diff.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/diff.go b/cmd/diff.go index a4f5474..8d62018 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -77,6 +77,9 @@ var diffCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { + spinner := newSpinner() + defer spinner.Stop() + spinner.Start() config := config.NewConfig() tmetricUserMe := tmetric.NewUser() @@ -130,6 +133,9 @@ var diffCmd = &cobra.Command{ totalTimeDiff := 0 for currentDay := start; !currentDay.After(end); currentDay = currentDay.AddDate(0, 0, 1) { + spinner.Stop() + spinner.Suffix = fmt.Sprintf(" %s", currentDay.Format("2006-01-02")) + spinner.Start() row := tableRow{} row.Date = currentDay.Format("2006-01-02") sumDurationTmetric := 0 @@ -223,6 +229,7 @@ var diffCmd = &cobra.Command{ }) outputTable.AppendSeparator() } + spinner.Stop() outputTable.AppendRow(table.Row{ "", "",