Skip to content

Commit

Permalink
refactor: rework client composition logic and remove unnecessary Togg…
Browse files Browse the repository at this point in the history
…l flag (#30)

* refactor: create Entries type
* refactor(tempo): use new Entries type and inline entry group upload
* refactor: add date time helpers
* refactor: reorganize client composition

Do a major refactor on how the client composition is done and simplify
client logic. Besides that, this commit reduces the duplicated logic
across clients and outsources the authentication setter.

Additionally, it makes it easier to integrate new clients with more
complex authentication methods, like Oauth2.

Also, the commit contains a change that removes the illogical flag for
setting toggl client base URL, hence that never changes.
  • Loading branch information
gabor-boros committed Oct 23, 2021
1 parent 4211197 commit 6658984
Show file tree
Hide file tree
Showing 21 changed files with 1,316 additions and 760 deletions.
185 changes: 68 additions & 117 deletions cmd/root.go
Expand Up @@ -4,20 +4,19 @@ import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"os/exec"
"regexp"
"strings"
"time"

"github.com/gabor-boros/minutes/internal/pkg/client/toggl"

"github.com/gabor-boros/minutes/internal/pkg/client/timewarrior"

"github.com/gabor-boros/minutes/internal/cmd/utils"
"github.com/gabor-boros/minutes/internal/pkg/client/clockify"

"github.com/gabor-boros/minutes/internal/pkg/client/toggl"

"github.com/gabor-boros/minutes/internal/cmd/utils"
"github.com/gabor-boros/minutes/internal/pkg/client/tempo"

"github.com/jedib0t/go-pretty/v6/progress"
Expand Down Expand Up @@ -161,7 +160,6 @@ func initTimewarriorFlags() {
}

func initTogglFlags() {
rootCmd.Flags().StringP("toggl-url", "", "https://api.track.toggl.com", "set the base URL")
rootCmd.Flags().StringP("toggl-api-key", "", "", "set the API key")
rootCmd.Flags().IntP("toggl-workspace", "", 0, "set the workspace ID")
}
Expand Down Expand Up @@ -238,109 +236,65 @@ func validateFlags() {
}
}

func getClientOpts(urlFlag string, usernameFlag string, passwordFlag string, tokenFlag string, tokenHeader string) (*client.BaseClientOpts, error) {
opts := &client.BaseClientOpts{
HTTPClientOpts: client.HTTPClientOpts{
HTTPClient: http.DefaultClient,
TokenHeader: tokenHeader,
},
TagsAsTasks: viper.GetBool("tags-as-tasks"),
TagsAsTasksRegex: viper.GetString("tags-as-tasks-regex"),
}

baseURL, err := url.Parse(viper.GetString(urlFlag))
if err != nil {
return opts, err
}

if usernameFlag != "" {
opts.Username = viper.GetString(usernameFlag)
}

if passwordFlag != "" {
opts.Password = viper.GetString(passwordFlag)
}

if tokenFlag != "" {
opts.Token = viper.GetString(tokenFlag)
}

opts.BaseURL = baseURL.String()

return opts, nil
}

func getFetcher() (client.Fetcher, error) {
switch viper.GetString("source") {
case "clockify":
opts, err := getClientOpts(
"clockify-url",
"",
"",
"clockify-api-key",
"X-Api-Key",
)

if err != nil {
return nil, err
}

return clockify.NewClient(&clockify.ClientOpts{
BaseClientOpts: *opts,
Workspace: viper.GetString("clockify-workspace"),
}), nil
return clockify.NewFetcher(&clockify.ClientOpts{
BaseClientOpts: client.BaseClientOpts{
TagsAsTasks: viper.GetBool("tags-as-tasks"),
TagsAsTasksRegex: viper.GetString("tags-as-tasks-regex"),
Timeout: 0,
},
TokenAuth: client.TokenAuth{
Header: "X-Api-Key",
Token: viper.GetString("clockify-api-key"),
},
BaseURL: viper.GetString("clockify-url"),
Workspace: viper.GetString("clockify-workspace"),
})
case "tempo":
opts, err := getClientOpts(
"tempo-url",
"tempo-username",
"tempo-password",
"",
"",
)

if err != nil {
return nil, err
}

return tempo.NewClient(&tempo.ClientOpts{
BaseClientOpts: *opts,
}), nil
return tempo.NewFetcher(&tempo.ClientOpts{
BaseClientOpts: client.BaseClientOpts{
TagsAsTasks: viper.GetBool("tags-as-tasks"),
TagsAsTasksRegex: viper.GetString("tags-as-tasks-regex"),
Timeout: 0,
},
BasicAuth: client.BasicAuth{
Username: viper.GetString("tempo-username"),
Password: viper.GetString("tempo-password"),
},
BaseURL: viper.GetString("tempo-url"),
})
case "timewarrior":
opts := &client.BaseClientOpts{
TagsAsTasks: viper.GetBool("tags-as-tasks"),
TagsAsTasksRegex: viper.GetString("tags-as-tasks-regex"),
}

return timewarrior.NewClient(&timewarrior.ClientOpts{
BaseClientOpts: *opts,
Command: viper.GetString("timewarrior-command"),
CommandArguments: viper.GetStringSlice("timewarrior-arguments"),
CommandCtxExecutor: exec.CommandContext,
UnbillableTag: viper.GetString("timewarrior-unbillable-tag"),
ClientTagRegex: viper.GetString("timewarrior-client-tag-regex"),
ProjectTagRegex: viper.GetString("timewarrior-project-tag-regex"),
return timewarrior.NewFetcher(&timewarrior.ClientOpts{
BaseClientOpts: client.BaseClientOpts{
TagsAsTasks: viper.GetBool("tags-as-tasks"),
TagsAsTasksRegex: viper.GetString("tags-as-tasks-regex"),
Timeout: 0,
},
CLIClient: client.CLIClient{
Command: viper.GetString("timewarrior-command"),
CommandArguments: viper.GetStringSlice("timewarrior-arguments"),
CommandCtxExecutor: exec.CommandContext,
},
UnbillableTag: viper.GetString("timewarrior-unbillable-tag"),
ClientTagRegex: viper.GetString("timewarrior-client-tag-regex"),
ProjectTagRegex: viper.GetString("timewarrior-project-tag-regex"),
})
case "toggl":
opts, err := getClientOpts(
"toggl-url",
"toggl-api-key",
"",
"",
"",
)

// Toggl requires basic auth with the token set as the username and
// "api_token" set for password as a fix value to access their APIs
opts.Password = "api_token"

if err != nil {
return nil, err
}

return toggl.NewClient(&toggl.ClientOpts{
BaseClientOpts: *opts,
Workspace: viper.GetInt("toggl-workspace"),
}), nil
return toggl.NewFetcher(&toggl.ClientOpts{
BaseClientOpts: client.BaseClientOpts{
TagsAsTasks: viper.GetBool("tags-as-tasks"),
TagsAsTasksRegex: viper.GetString("tags-as-tasks-regex"),
Timeout: 0,
},
BasicAuth: client.BasicAuth{
Username: viper.GetString("toggl-api-key"),
Password: "api_token",
},
BaseURL: "https://api.track.toggl.com",
Workspace: viper.GetInt("toggl-workspace"),
})
default:
return nil, ErrNoSourceImplementation
}
Expand All @@ -349,21 +303,18 @@ func getFetcher() (client.Fetcher, error) {
func getUploader() (client.Uploader, error) {
switch viper.GetString("target") {
case "tempo":
opts, err := getClientOpts(
"tempo-url",
"tempo-username",
"tempo-password",
"",
"",
)

if err != nil {
return nil, err
}

return tempo.NewClient(&tempo.ClientOpts{
BaseClientOpts: *opts,
}), nil
return tempo.NewUploader(&tempo.ClientOpts{
BaseClientOpts: client.BaseClientOpts{
TagsAsTasks: viper.GetBool("tags-as-tasks"),
TagsAsTasksRegex: viper.GetString("tags-as-tasks-regex"),
Timeout: 0,
},
BasicAuth: client.BasicAuth{
Username: viper.GetString("tempo-username"),
Password: viper.GetString("tempo-password"),
},
BaseURL: viper.GetString("tempo-url"),
})
default:
return nil, ErrNoTargetImplementation
}
Expand Down
6 changes: 3 additions & 3 deletions internal/cmd/utils/printer.go
Expand Up @@ -60,7 +60,7 @@ type TableColumnConfig struct {
type Printer interface {
// Print prints out the list of complete and incomplete entries.
// The output location must be set through `BasePrinterOpts`.
Print(completeEntries []worklog.Entry, incompleteEntries []worklog.Entry) error
Print(completeEntries worklog.Entries, incompleteEntries worklog.Entries) error
}

// BasePrinterOpts represents the configuration for common printer options.
Expand Down Expand Up @@ -111,7 +111,7 @@ func (p *tablePrinter) convertEntryToRow(entry *worklog.Entry) table.Row {
}
}

func (p *tablePrinter) generateRows(entries []worklog.Entry, billable *time.Duration, unbillable *time.Duration) {
func (p *tablePrinter) generateRows(entries worklog.Entries, billable *time.Duration, unbillable *time.Duration) {
for i := range entries {
entry := entries[i]
*billable += entry.BillableDuration
Expand All @@ -120,7 +120,7 @@ func (p *tablePrinter) generateRows(entries []worklog.Entry, billable *time.Dura
}
}

func (p *tablePrinter) Print(completeEntries []worklog.Entry, incompleteEntries []worklog.Entry) error {
func (p *tablePrinter) Print(completeEntries worklog.Entries, incompleteEntries worklog.Entries) error {
var totalBillable time.Duration
var totalUnbillable time.Duration

Expand Down

0 comments on commit 6658984

Please sign in to comment.