Skip to content

Commit

Permalink
fix: 💹 Support for Excel (#22)
Browse files Browse the repository at this point in the history
* chore: added csv stuct tags

* chore: prep for CVS encoding

* chore: added logic for generating excel

* fix: added support for CSV

* chore: fix lint error

* ci: debug test errors

* chore: test with 1.22.5

* ci: update test

* test: fix test case breaking CI

* ci: add logic to update code coverage

* docs: update licenses
  • Loading branch information
karl-cardenas-coding authored Aug 16, 2024
1 parent bdc5af3 commit 91a7e33
Show file tree
Hide file tree
Showing 27 changed files with 7,173 additions and 186 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/codecoverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Update Coverage

on:
push:
branches: [main]

jobs:
coverage:
name: Update Code Coverage
runs-on: ubuntu-latest
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4

- name: Set up Go
uses: actions/[email protected]
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
id: go

- name: "Get dependencies"
id: dependencies
run: |
go get
- name: Go Tests
run: |
mkdir -p tests/data
make tests-coverage
- name: Upload coverage reports to Codecov
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}
6 changes: 3 additions & 3 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@v4

- name: Set up Go
uses: actions/[email protected].1
uses: actions/[email protected].2
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
uses: actions/checkout@v4

- name: Set up Go
uses: actions/[email protected].1
uses: actions/[email protected].2
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
Expand All @@ -75,7 +75,7 @@ jobs:
uses: actions/checkout@v4

- name: Set up Go
uses: actions/[email protected].1
uses: actions/[email protected].2
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:

- run: git fetch --force --tags

- uses: actions/setup-go@v4
- uses: actions/setup-go@v5.0.2
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
Expand Down
9 changes: 3 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ license:

opensource:
@echo "Checking for open source licenses"
~/go/bin/go-licenses report github.com/karl-cardenas-coding/mywhoop --ignore $$(go list -m) --include_tests \
--ignore $$(tr -d ' \n' <<<"$${{ inputs.ignore-modules }}") \
--ignore $$(tr -d ' \n' <<<"$${{ inputs.ignore-modules }}") \
--ignore $$(go list std | awk 'NR > 1 { printf(",") } { printf("%s",$0) } END { print "" }') \
--template=docs/open-source.tpl > docs/open-source.md
~/go/bin/go-licenses report github.com/karl-cardenas-coding/mywhoop --template=docs/open-source.tpl > docs/open-source.md


tests: ## Run tests
@echo "Running tests"
Expand All @@ -25,7 +22,7 @@ tests: ## Run tests

tests-coverage: ## Start Go Test with code coverage
@echo "Running Go Tests with code coverage"
go test -race -shuffle on -cover -coverprofile=coverage.out -covermode=atomic ./...
go test -v -race -shuffle on -cover -coverprofile=coverage.out -covermode=atomic ./...

view-coverage: ## View the code coverage
@echo "Viewing the code coverage"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ mywhoop dump
| ------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------- |
| `--location` | `-l` | The location to save the Whoop data file. | No | `./data/` |
| `--filter` | `-f` | Specify a filter string to filter the data. For example to download all the data from January 2024 `start=2024-01-01T00:00:00.000Z&end=2024-01-31T00:00:00.000Z`. You can learn more about the filter syntax in the Whoop API [Pagination](https://developer.whoop.com/docs/developing/pagination) documentation. | No | `""` |
| `--output` | `-o` | The output format. Supported types are `json` or `xlsx`. | No | `json` |

#### Filter

Expand Down
72 changes: 56 additions & 16 deletions cmd/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"log/slog"
"os"
"strings"

"github.com/karl-cardenas-coding/mywhoop/internal"
"github.com/spf13/cobra"
Expand All @@ -16,6 +17,7 @@ import (
var (
dataLocation string
filter string
output string
)

var dumpCmd = &cobra.Command{
Expand All @@ -32,6 +34,7 @@ var dumpCmd = &cobra.Command{
func init() {
dumpCmd.PersistentFlags().StringVarP(&dataLocation, "location", "l", "", "The location to dump the data to. Default is the current directory's data/ folder.")
dumpCmd.PersistentFlags().StringVarP(&filter, "filter", "f", "", "Provide a filter string to narrow down the data to download. For example, start=2024-01-01T00:00:00.000Z&end=2022-04-01T00:00:00.000Z")
dumpCmd.PersistentFlags().StringVarP(&output, "output", "o", "json", "The output format. Supported types are json or csv. Default is json.")

rootCmd.AddCommand(dumpCmd)
}
Expand All @@ -51,6 +54,20 @@ func dump(ctx context.Context) error {
cfg := Configuration
cfg.Server.Enabled = false

if filter != "" {
slog.Info("Filtering data with:", "filter", filter)
}

if output != "" {
slog.Info("Output format set to", "output", output)
}

cliFlags := cliFlags{
dataLocation: dataLocation,
filter: filter,
output: strings.ToLower(output),
}

ok, token, err := internal.VerfyToken(cfg.Credentials.CredentialsFile)
if err != nil {
slog.Error("unable to verify token", "error", err)
Expand All @@ -66,10 +83,6 @@ func dump(ctx context.Context) error {
return err
}

if filter != "" {
slog.Info("Filtering data with:", "filter", filter)
}

data, err := user.GetUserProfileData(ctx, client, internal.DEFAULT_WHOOP_API_USER_DATA_URL, token.AccessToken, ua)
if err != nil {
internal.LogError(err)
Expand Down Expand Up @@ -104,7 +117,7 @@ func dump(ctx context.Context) error {
return err
}

sleep.NextToken = ""
sleep.NextToken = nil
user.SleepCollection = *sleep

recovery, err := user.GetRecoveryCollection(ctx, client, internal.DEFAULT_WHOOP_API_RECOVERY_DATA_URL, token.AccessToken, filter, ua)
Expand All @@ -117,7 +130,7 @@ func dump(ctx context.Context) error {
return err
}

recovery.NextToken = ""
recovery.NextToken = nil
user.RecoveryCollection = *recovery

workout, err := user.GetWorkoutCollection(ctx, client, internal.DEFAULT_WHOOP_API_WORKOUT_DATA_URL, token.AccessToken, filter, ua)
Expand All @@ -130,7 +143,7 @@ func dump(ctx context.Context) error {
return err
}

workout.NextToken = ""
workout.NextToken = nil
user.WorkoutCollection = *workout

cycle, err := user.GetCycleCollection(ctx, client, internal.DEFAULT_WHOOP_API_CYCLE_DATA_URL, token.AccessToken, filter, ua)
Expand All @@ -142,20 +155,47 @@ func dump(ctx context.Context) error {
}
return err
}
cycle.NextToken = ""
cycle.NextToken = nil
user.CycleCollection = *cycle

finalDataRaw, err := json.MarshalIndent(user, "", " ")
if err != nil {
internal.LogError(err)
notifyErr := notificationMethod.Publish(client, []byte(err.Error()), internal.EventErrors.String())
if notifyErr != nil {
slog.Error("unable to send notification", "error", notifyErr)
var finalDataRaw []byte
switch output {
case "json":
finalDataRaw, err = json.MarshalIndent(user, "", " ")
if err != nil {
internal.LogError(err)
notifyErr := notificationMethod.Publish(client, []byte(err.Error()), internal.EventErrors.String())
if notifyErr != nil {
slog.Error("unable to send notification", "error", notifyErr)
}
return err
}
return err

case "xlsx":
finalDataRaw, err = internal.ConvertToExcel(user)
if err != nil {
internal.LogError(err)
notifyErr := notificationMethod.Publish(client, []byte(err.Error()), internal.EventErrors.String())
if notifyErr != nil {
slog.Error("unable to send notification", "error", notifyErr)
}
return err
}

default:
finalDataRaw, err = json.MarshalIndent(user, "", " ")
if err != nil {
internal.LogError(err)
notifyErr := notificationMethod.Publish(client, []byte(err.Error()), internal.EventErrors.String())
if notifyErr != nil {
slog.Error("unable to send notification", "error", notifyErr)
}
return err
}

}

exporterMethod, err := determineExporterExtension(cfg, client, dataLocation)
exporterMethod, err := determineExporterExtension(cfg, client, cliFlags)
if err != nil {
slog.Error("unable to determine export method", "error", err)
notifyErr := notificationMethod.Publish(client, []byte(err.Error()), internal.EventErrors.String())
Expand Down
44 changes: 36 additions & 8 deletions cmd/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func determineNotificationExtension(cfg internal.ConfigurationData) (internal.No

// determineExporterExtension determines the export extension to use and returns the appropriate export.
// The parameter isServerMode is used to determine if the exporter is being used in server mode. Use this flag to set server mode defaults.
func determineExporterExtension(cfg internal.ConfigurationData, client *http.Client, dataLocation string) (internal.Export, error) {
func determineExporterExtension(cfg internal.ConfigurationData, client *http.Client, cFlags cliFlags) (internal.Export, error) {

var (
filePath string
Expand All @@ -53,12 +53,16 @@ func determineExporterExtension(cfg internal.ConfigurationData, client *http.Cli

switch cfg.Export.Method {
case "file":
if dataLocation == "" {
if cFlags.dataLocation == "" {
filePath = cfg.Export.FileExport.FilePath
}

if dataLocation != "" {
filePath = dataLocation
if cFlags.dataLocation != "" {
filePath = cFlags.dataLocation
}

if cFlags.output != "" {
cfg.Export.FileExport.FileType = cFlags.output
}

fileExp := export.NewFileExport(filePath,
Expand All @@ -72,8 +76,12 @@ func determineExporterExtension(cfg internal.ConfigurationData, client *http.Cli

case "s3":
slog.Info("AWS S3 export method specified")
if dataLocation != "" {
cfg.Export.AWSS3.FileConfig.FilePath = dataLocation
if cFlags.dataLocation != "" {
cfg.Export.AWSS3.FileConfig.FilePath = cFlags.dataLocation
}

if cFlags.output != "" {
cfg.Export.AWSS3.FileConfig.FileType = cFlags.output
}

awsS3, err := export.NewAwsS3Export(cfg.Export.AWSS3.Region,
Expand All @@ -89,11 +97,16 @@ func determineExporterExtension(cfg internal.ConfigurationData, client *http.Cli
exporter = awsS3

default:
if dataLocation == "" {
if cFlags.dataLocation == "" {
filePath = cfg.Export.FileExport.FilePath
} else {
filePath = dataLocation
filePath = cFlags.dataLocation
}

if cFlags.output != "" {
cfg.Export.FileExport.FileType = cFlags.output
}

slog.Info("no valid export method specified. Defaulting to file.")

fileExp := export.NewFileExport(filePath,
Expand All @@ -109,3 +122,18 @@ func determineExporterExtension(cfg internal.ConfigurationData, client *http.Cli
return exporter, nil

}

// getFileType determines the file type to use for the export based on the configuration and command line flags.
func getFileType(cfg internal.ConfigurationData) string {

if cfg.Export.FileExport.FileType != "" {
return cfg.Export.FileExport.FileType
}

if cfg.Export.AWSS3.FileConfig.FileType != "" {
return cfg.Export.AWSS3.FileConfig.FileType
}

return "json"

}
Loading

0 comments on commit 91a7e33

Please sign in to comment.