diff --git a/cmd/diff.go b/cmd/diff.go index e1b607c..8d62018 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -41,9 +41,10 @@ type tableRow struct { OpenProjectEntry string OpenProjectDuration string DiffInTime string + 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 @@ -76,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() @@ -119,7 +123,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{ @@ -129,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 @@ -177,7 +184,30 @@ 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 += "- inactive\n project\n" + countWarnings = countWarnings + 2 + } + if !workPackage.Embedded.Project.Favorited { + 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 += "- 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" + } + } + } if sumDurationTmetric > sumDurationOpenProject { diff := sumDurationTmetric - sumDurationOpenProject @@ -188,7 +218,6 @@ var diffCmd = &cobra.Command{ row.DiffInTime = strconv.Itoa(diff) totalTimeDiff += diff } - outputTable.AppendRow(table.Row{ row.Date, strings.Trim(row.TmetricEntry, "\n"), @@ -196,9 +225,11 @@ var diffCmd = &cobra.Command{ strings.Trim(row.OpenProjectEntry, "\n"), strings.Trim(row.OpenProjectDuration, "\n"), row.DiffInTime, + row.Warnings, }) outputTable.AppendSeparator() } + spinner.Stop() outputTable.AppendRow(table.Row{ "", "", 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 {