Skip to content

Commit

Permalink
24082024 dynamic logging (#6)
Browse files Browse the repository at this point in the history
* Cleanup log.go

* rolling adaptive logging

* removed extra prints

* rearrange folder structure

* refactor log cmd, updated .gitignore

---------

Co-authored-by: Philipp Galichkin <[email protected]>
  • Loading branch information
PhilGal and Philipp Galichkin authored Aug 29, 2024
1 parent 1e9ab14 commit 9ee8466
Show file tree
Hide file tree
Showing 31 changed files with 389 additions and 374 deletions.
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
jtl
jtl.exe
jtl.log
todo.txt
todo.txt

bin
.idea
.fleet
.vscode

.DS_Store
36 changes: 0 additions & 36 deletions .vscode/launch.json

This file was deleted.

1 change: 0 additions & 1 deletion .vscode/settings.json

This file was deleted.

10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ These are common Jtl commands used in various situations:
* log your work as you go to a local file (see: `jtl help log`),
* display summary report (see: `jtl help report`),
* finally, push all data from file to your company's remote server (see: `jtl help push`)

For better experience, it is recommended to add a valid configuration file `$HOME/.jtl/config.yaml`. Type 'jtl help push' for more details.

When you call any command, a programm is trying to locate a data file `$HOME/data/<month-year>.csv` Thus, each month you'll have a new data file.
Expand All @@ -24,7 +24,7 @@ Once you get your executable, install/run it as you would usually install or run

### Build from source

Install go >= 1.14.x
Install Go on your machine. You can download it from [here](https://golang.org/dl/).

```
❯ git clone https://github.com/PhilGal/jtl.git
Expand All @@ -36,9 +36,7 @@ Running `go install` will put an executable into your `$GOPATH` directory. Make

```
❯ echo $GOPATH
/usr/local/Cellar/go/1.14.1
/usr/local/Cellar/go/{version}
❯ which jtl
/usr/local/Cellar/go/1.14.1/bin/jtl
/usr/local/Cellar/go/{version}/bin/jtl
```


Binary file removed bin/osx/jtl-osx-x64
Binary file not shown.
158 changes: 37 additions & 121 deletions cmd/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,29 @@
package cmd

import (
"fmt"
"log"
"math"
"slices"
"time"

"github.com/philgal/jtl/cmd/duration"
"github.com/philgal/jtl/cmd/internal/config"
"github.com/philgal/jtl/cmd/internal/csv"
"github.com/philgal/jtl/internal/config"
"github.com/philgal/jtl/internal/log"
"github.com/philgal/jtl/internal/model"
"github.com/spf13/cobra"
)

var (
// Args
ticket string
// Flags
timeSpent string
comment string
startedTs string
autoFitting bool
)

const (
ticketCmdStr = "ticket"
timeCmdStr = "time"
dateCmdStr = "date"
messageCmdStr = "message"
)

// logCmd represents the log command
var logCmd = &cobra.Command{
Use: "log",
Expand All @@ -48,123 +59,28 @@ Examples:
jtl log -j JIRA-101 -t 30m -s "14 Apr 2020 10:00" -m "Comment"
jtl log -j l666 -t 1h -s "06 Jun 2020 06:00" -m "Some repeating meeting!"
`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
//use App for testing
runLogCommand(cmd, args)
ticket = args[0]
model.ValidateJiraTicketFormat(ticket)
executorArgs := log.ExecutorArgs{
Ticket: ticket,
TimeSpent: timeSpent,
Comment: comment,
StartedTs: startedTs}
if autoFitting {
log.AutoFitting{ExecutorArgs: executorArgs}.Execute()
} else {
log.Normal{ExecutorArgs: executorArgs}.Execute()
}
displayReport()
},
}

const (
ticketCmdStr = "ticket"
timeCmdStr = "time"
dateCmdStr = "date"
messageCmdStr = "message"
)

// fixme: make day start time configurable
var now = time.Now()
var dayStart = time.Date(now.Year(), now.Month(), now.Day(), 8, 45, 0, 0, time.Local)

func init() {

rootCmd.AddCommand(logCmd)
//todo improve duration parsing
logCmd.Flags().StringP(timeCmdStr, "t", "4h", "[Required] Time spent. Default - 4h")
logCmd.Flags().StringP(messageCmdStr, "m", "wip", "Comment to the work log. Will be displayed in Jira. Default - \"wip\"")
logCmd.Flags().StringP(dateCmdStr, "d", dayStart.Format(config.DefaultDateTimePattern), "Date and time when the work has been started. Default - current timestamp")
}

func runLogCommand(cmd *cobra.Command, args []string) {

if len(args) < 1 {
log.Fatal("too few arguments")
}

var ticket, timeSpent, startedTs, comment string

ticket = args[0]
if len(ticket) < 5 {
log.Fatalf("invalid ticket %s", ticket)
}

timeSpent, _ = cmd.Flags().GetString(timeCmdStr)
comment, _ = cmd.Flags().GetString(messageCmdStr)
startedTs, _ = cmd.Flags().GetString(dateCmdStr)
fcsv := csv.NewCsvFile(config.DataFilePath())
fcsv.ReadAll()

// total: 4h before pause, 4h after
// traverse from most recent record
// sum hours logged in the same day as `startedTs`
// case =0: log 4h from 8:45, 4h from 13:00
// case >0:

// calculate how much is left of the startedTs
slices.SortFunc(fcsv.Records, func(a csv.CsvRec, b csv.CsvRec) int {
return duration.ParseTimeTruncatedToDate(a.StartedTs).Compare(duration.ParseTimeTruncatedToDate(b.StartedTs))
})

logDate, _ := time.Parse(config.DefaultDateTimePattern, startedTs)
//if dates are equal, count hours
sameDateRecs := sameDateRecords(&fcsv.Records, logDate)

minutesSpentToDate := timeSpentToDateInMin(&sameDateRecs, logDate)
// for example 500 > 480 -> 20m to log
// fixme: make max daily duration configurable
if minutesSpentToDate >= duration.EightHoursInMin {
fmt.Printf("You have already logged %s, will not log more\n", duration.ToString(minutesSpentToDate))
return
}

fmt.Printf("Same date records: %v\n", sameDateRecs)

if duration.ToMinutes(timeSpent)+minutesSpentToDate <= duration.EightHoursInMin {
timeSpentMin := int(math.Min(float64(duration.EightHoursInMin-minutesSpentToDate), duration.EightHoursInMin/2))
timeSpent = duration.ToString(timeSpentMin)
fmt.Printf("Time spent will is trimmed to %s, to not to exceed %s\n",
timeSpent,
duration.ToString(duration.EightHoursInMin))
// adjust startedTs to the last record: last rec.StartedTs + calculated time spent = new startedTs
if reclen := len(sameDateRecs); reclen > 0 {
lastRec := sameDateRecs[reclen-1]
lastRecStaredAt := duration.ParseTime(lastRec.StartedTs)
startedTs = lastRecStaredAt.Add(time.Minute * time.Duration(duration.ToMinutes(lastRec.TimeSpent))).Format(config.DefaultDateTimePattern)
}
}

if timeSpent != "0m" {
fcsv.AddRecord(csv.CsvRec{
ID: "",
StartedTs: startedTs,
Comment: comment,
TimeSpent: timeSpent,
Ticket: ticket,
})
log.Print(fcsv.Records)
fcsv.Write()
} else {
fmt.Println("Calculated time spent is 0m, will not log!")
}
}

func timeSpentToDateInMin(sameDateRecs *csv.CsvRecords, logDate time.Time) int {
var totalTimeSpentOnDate int
for _, rec := range slices.Backward(*sameDateRecs) {
recDate, _ := time.Parse(config.DefaultDateTimePattern, rec.StartedTs)
// logs files are already collected by months of the year, so it's enough to compare days
if recDate.Day() == logDate.Day() {
totalTimeSpentOnDate += duration.ToMinutes(rec.TimeSpent)
} else {
// items are sorted, so first occurrence of mismatched date means there will no more matches.
return totalTimeSpentOnDate
}
}
return totalTimeSpentOnDate
}

func sameDateRecords(recs *csv.CsvRecords, logDate time.Time) csv.CsvRecords {
return (*recs).Filter(func(rec csv.CsvRec) bool {
return duration.ParseTimeTruncatedToDate(rec.StartedTs).Equal(logDate.Truncate(24 * time.Hour))
})
logCmd.Flags().StringVarP(&timeSpent, timeCmdStr, "t", config.DefaultTicketDuration, "[Required] Time spent. Default - 4h")
logCmd.Flags().StringVarP(&comment, messageCmdStr, "m", "wip", "Comment to the work log. Will be displayed in Jira. Default - \"wip\"")
logCmd.Flags().StringVarP(&startedTs, dateCmdStr, "d", config.DefaultDayStart, "Date and time when the work has been started. Default - 8:45")
logCmd.Flags().BoolVarP(&autoFitting, "auto-fitting", "f", true, "Auto-fittimg mode adjusts not pushed records to fit the maximum *daily* duration. If false - logs whatever the input is! Default - true")
}
16 changes: 8 additions & 8 deletions cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import (
"sync"
"time"

"github.com/philgal/jtl/cmd/internal/config"
"github.com/philgal/jtl/cmd/internal/csv"
"github.com/philgal/jtl/cmd/internal/model"
"github.com/philgal/jtl/cmd/internal/rest"
"github.com/philgal/jtl/internal/config"
"github.com/philgal/jtl/internal/csv"
"github.com/philgal/jtl/internal/model"
"github.com/philgal/jtl/internal/rest"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
Expand All @@ -52,8 +52,8 @@ For correct work this command reqiures a configured <host> and user <credentials
However, if username and password are not defined, a user will be prompted to enter them.
Preview mode:
To make sure the data to be pushed is correct, the command can be executed with -p flag.
The preview output contains host, username and prepared requests bodies for POST request to Jira.
To make sure the data to be pushed is correct, the command can be executed with -p flag.
The preview output contains host, username and prepared requests bodies for POST request to Jira.
`,
Run: func(cmd *cobra.Command, args []string) {
PushToServer(cmd)
Expand All @@ -78,7 +78,7 @@ func PushToServer(cmd *cobra.Command) {
func push(cmd *cobra.Command, restClient rest.Client) {
csvFile := csv.NewCsvFile(config.DataFilePath())
csvFile.ReadAll()
jreq := model.NewJiraRequest(&csvFile.Records)
jreq := model.NewJiraRequest(csvFile.Records)

if viper.GetString("Host") == "" {
fmt.Println("Jira host is not set in config, printing preview")
Expand Down Expand Up @@ -155,7 +155,7 @@ func postSingleRequest(cred *model.Credentials, row model.JiraRequestRow, restCl
wg.Done()
}

func updatePushedRecordsIds(resp []model.JiraResponse, csvRecords csv.CsvRecords) {
func updatePushedRecordsIds(resp []model.JiraResponse, csvRecords []csv.Record) {
if len(resp) == 0 {
return
}
Expand Down
12 changes: 6 additions & 6 deletions cmd/push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import (
"os"
"testing"

"github.com/philgal/jtl/cmd/internal/csv"
"github.com/philgal/jtl/cmd/internal/model"
"github.com/philgal/jtl/cmd/internal/rest"
"github.com/philgal/jtl/validation"
"github.com/philgal/jtl/internal/csv"
"github.com/philgal/jtl/internal/model"
"github.com/philgal/jtl/internal/rest"
"github.com/philgal/jtl/internal/validation"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -64,7 +64,7 @@ func TestPost(t *testing.T) {
csvFile.ReadAll()

t.Run("Should unmarshall correct response", func(t *testing.T) {
jreq := model.NewJiraRequest(&csvFile.Records)
jreq := model.NewJiraRequest(csvFile.Records)
jres := post(&model.Credentials{}, jreq, restClient)

expected := []model.JiraResponse{{RowIdx: 1, Id: "100028", IssueId: "10002", Timespent: "3h 20m", Comment: "I did some work here.", Started: "2020-04-09T00:28:56.595+0000", IsSuccess: true}}
Expand All @@ -80,7 +80,7 @@ func TestUpdateAfterPost(t *testing.T) {
csvFile.ReadAll()

t.Run("Should update row with id from response", func(t *testing.T) {
jreq := model.NewJiraRequest(&csvFile.Records)
jreq := model.NewJiraRequest(csvFile.Records)
jres := post(&model.Credentials{}, jreq, restClient)

assert.Equal(t, "1", csvFile.Records[0].ID)
Expand Down
6 changes: 3 additions & 3 deletions cmd/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
package cmd

import (
"github.com/philgal/jtl/cmd/internal/config"
"github.com/philgal/jtl/cmd/internal/csv"
"github.com/philgal/jtl/cmd/internal/report"
"github.com/philgal/jtl/internal/config"
"github.com/philgal/jtl/internal/csv"
"github.com/philgal/jtl/internal/report"

"github.com/spf13/cobra"
)
Expand Down
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"fmt"
"os"

"github.com/philgal/jtl/cmd/internal/config"
"github.com/philgal/jtl/internal/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand All @@ -33,7 +33,7 @@ var rootCmd = &cobra.Command{
# log your work as you go to a local file (see: 'jtl help log'),
# display summary report (see: 'jtl help report'),
# finally, push all data from file to your company's remote server (see: 'jtl help push')
For better experience, it is recommended to add a valid configuration file $HOME/.jtl/config.yaml. Type 'jtl help push' for more details.
When you call any command, a programm is trying to locate a data file $HOME/data/<month-year>.csv Thus, each month you'll have a new data file.
Expand Down
1 change: 0 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
alias: {}
credentials:
password: ""
username: ""
Expand Down
7 changes: 2 additions & 5 deletions example-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
host: https://jira.server.url
credentials:
username: <username>
password: <password>
alias:
jt1: JIRATICKET-1
l666: ANOTHERLONGTICKET-666
username: <username>
password: <password>
Binary file added internal/.DS_Store
Binary file not shown.
Loading

0 comments on commit 9ee8466

Please sign in to comment.