Skip to content

feat: added export functionality#172

Open
fabricioereche wants to merge 3 commits into
dhth:mainfrom
fabricioereche:main
Open

feat: added export functionality#172
fabricioereche wants to merge 3 commits into
dhth:mainfrom
fabricioereche:main

Conversation

@fabricioereche

Copy link
Copy Markdown

Summary

Adds JSON and CSV export support to the report, log, and stats commands via a new --format / -f flag. The default behavior (plain) is unchanged and still renders the existing ASCII table output.

CLI changes

  • Added --format / -f flag to report, log, and stats
  • Accepted values: plain (default), json, csv
  • Updated command help text to mention the new export option
  • Each command parses the format with types.ParseOutputFormat() and passes it into the corresponding render function

Examples

hours report today --format json
hours report 3d --format csv --agg
hours log today -f csv
hours stats all --format json

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This change adds export format support (JSON and CSV) to the report, log, and stats CLI subcommands via a new --format/-f flag. A new OutputFormat type with parsing utilities is added to the types package. A new export.go file provides data conversion and writer helpers for JSON/CSV output. The RenderReport, RenderTaskLog, and RenderStats functions are updated to accept a format parameter and route non-plain formats through export logic, rejecting interactive mode for non-plain formats. CLI snapshot tests cover the new export behavior and error cases.

Changes

Cohort / File(s) Summary
internal/types/types.go Adds OutputFormat enum, ParseOutputFormat, String(), ValidOutputFormatValues, and ErrIncorrectOutputFormatProvided
internal/ui/export.go New file with export data models and JSON/CSV writer helpers for report, log, and stats data
internal/ui/report.go, internal/ui/log.go, internal/ui/stats.go Adds format parameter, export dispatch, and data-collection helpers
cmd/root.go Adds --format/-f flag, parses format, updates render calls and help text
tests/cli/export_test.go New snapshot tests for export formats and error cases

Sequence Diagram(s)

sequenceDiagram
  participant CLI as cmd/root.go
  participant RenderReport
  participant collectReportData
  participant renderReportExport
  CLI->>RenderReport: call with format
  RenderReport->>RenderReport: check interactive vs format
  RenderReport->>collectReportData: fetch entries
  RenderReport->>renderReportExport: write JSON/CSV
  renderReportExport-->>CLI: output written
Loading

Estimated code review effort: 3.5/5 (High for report.go and log.go due to control-flow refactoring; Medium elsewhere)

Related issues: Not specified in provided context.

Related PRs: Not specified in provided context.

Suggested labels: enhancement, cli

Suggested reviewers: dhth

🥕 A poem for the burrow:

A hop, a flag, a format new,
JSON hops out, CSV too,
Tables once ruled the terminal ground,
Now data exports without a sound,
This rabbit's proud of the code review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change, but it is broad and omits the specific commands and formats added.
Description check ✅ Passed The description accurately summarizes the JSON/CSV export support, flags, and default behavior changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
internal/ui/export.go (1)

90-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

toExportAggEntry and toExportStatsEntry are duplicate implementations.

Both functions have identical bodies converting types.TaskReportEntry into structurally identical structs (exportAggEntry and exportStatsEntry, both with TaskID/Task/NumEntries/SecsSpent/TimeSpent). Consider consolidating into a single struct/function and reusing it for both report-agg and stats exports.

♻️ Proposed consolidation
-type exportAggEntry struct {
-	TaskID     int    `json:"taskId"`
-	Task       string `json:"task"`
-	NumEntries int    `json:"numEntries"`
-	SecsSpent  int    `json:"secsSpent"`
-	TimeSpent  string `json:"timeSpent"`
-}
-
-type exportStatsEntry struct {
-	TaskID     int    `json:"taskId"`
-	Task       string `json:"task"`
-	NumEntries int    `json:"numEntries"`
-	SecsSpent  int    `json:"secsSpent"`
-	TimeSpent  string `json:"timeSpent"`
-}
+type exportAggEntry struct {
+	TaskID     int    `json:"taskId"`
+	Task       string `json:"task"`
+	NumEntries int    `json:"numEntries"`
+	SecsSpent  int    `json:"secsSpent"`
+	TimeSpent  string `json:"timeSpent"`
+}
+
+// exportStatsEntry reuses exportAggEntry's shape
+type exportStatsEntry = exportAggEntry

And drop toExportStatsEntry in favor of toExportAggEntry.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/ui/export.go` around lines 90 - 108, `toExportAggEntry` and
`toExportStatsEntry` in export.go are duplicate converters with identical output
shapes, so consolidate them into one shared conversion path. Reuse
`toExportAggEntry` for both report-agg and stats export flows, and remove
`toExportStatsEntry` if it is no longer needed; update the callers in the export
code to use the remaining helper so both `exportAggEntry` and `exportStatsEntry`
are populated through the same logic.
tests/cli/export_test.go (1)

24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract repeated CLI flag literals into shared constants.

Static analysis flags --format (12 occurrences) and --interactive (3 occurrences) as candidates for goconst. Low priority for a test file but easy to address.

Also applies to: 135-138

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cli/export_test.go` at line 24, The test cases in the CLI export suite
are repeating literal flag strings, so extract the shared CLI flag values into
constants and reuse them across the affected tests. Update the table-driven
cases in the export tests, including the “json today” case and the other
occurrences referenced by the review, to use the shared constants for --format
and --interactive instead of inline string literals.

Source: Linters/SAST tools

cmd/root.go (1)

656-676: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

--plain/-p is silently ignored when --format is json/csv.

Downstream RenderReport/RenderTaskLog/RenderStats branch on format != types.OutputFormatPlain before the plain bool is ever consulted, so passing both -p and --format json|csv silently drops the -p flag with no error or warning. The updated help text doesn't call out this interaction either.

Consider using cmd.MarkFlagsMutuallyExclusive("plain", "format") (or an explicit check in RunE) to fail fast instead of silently ignoring one of the flags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/root.go` around lines 656 - 676, The report/log/stats commands currently
ignore --plain/-p whenever --format is json or csv, because the rendering path
checks outputFormat before the plain flag is used. Update the command setup in
cmd/root.go for reportCmd, logCmd, and statsCmd so this conflict is handled
explicitly, either by marking plain and format as mutually exclusive or by
validating the combination in the RunE handlers and returning an error. Make
sure the behavior is clear in the CLI contract and that the relevant flag
definitions and command handlers stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/ui/export.go`:
- Around line 178-213: The CSV writer functions are checking w.Error() before
the final buffered data is flushed, so last-write failures can be missed. Update
writeReportCSV, writeReportAggCSV, writeLogCSV, and writeStatsCSV to call
w.Flush() explicitly before returning, then return w.Error() after that flush.
Remove reliance on defer w.Flush() in these functions so the flush happens
synchronously at the end of each writer.

---

Nitpick comments:
In `@cmd/root.go`:
- Around line 656-676: The report/log/stats commands currently ignore --plain/-p
whenever --format is json or csv, because the rendering path checks outputFormat
before the plain flag is used. Update the command setup in cmd/root.go for
reportCmd, logCmd, and statsCmd so this conflict is handled explicitly, either
by marking plain and format as mutually exclusive or by validating the
combination in the RunE handlers and returning an error. Make sure the behavior
is clear in the CLI contract and that the relevant flag definitions and command
handlers stay aligned.

In `@internal/ui/export.go`:
- Around line 90-108: `toExportAggEntry` and `toExportStatsEntry` in export.go
are duplicate converters with identical output shapes, so consolidate them into
one shared conversion path. Reuse `toExportAggEntry` for both report-agg and
stats export flows, and remove `toExportStatsEntry` if it is no longer needed;
update the callers in the export code to use the remaining helper so both
`exportAggEntry` and `exportStatsEntry` are populated through the same logic.

In `@tests/cli/export_test.go`:
- Line 24: The test cases in the CLI export suite are repeating literal flag
strings, so extract the shared CLI flag values into constants and reuse them
across the affected tests. Update the table-driven cases in the export tests,
including the “json today” case and the other occurrences referenced by the
review, to use the shared constants for --format and --interactive instead of
inline string literals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f59d18dd-c85d-4161-863a-7515e8918254

📥 Commits

Reviewing files that changed from the base of the PR and between 389d030 and a02b49d.

⛔ Files ignored due to path filters (20)
  • tests/cli/__snapshots__/TestExportFormatErrors_log_interactive_json_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestExportFormatErrors_report_incorrect_format_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestExportFormatErrors_report_interactive_json_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestExportFormatErrors_stats_interactive_json_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestLogExport_csv_3d_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestLogExport_csv_today_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestLogExport_json_3d_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestLogExport_json_today_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestReportExport_csv_3d_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestReportExport_csv_agg_today_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestReportExport_csv_today_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestReportExport_json_3d_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestReportExport_json_agg_today_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestReportExport_json_today_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestStatsExport_csv_3d_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestStatsExport_csv_all_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestStatsExport_csv_today_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestStatsExport_json_3d_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestStatsExport_json_all_1.snap is excluded by !**/*.snap
  • tests/cli/__snapshots__/TestStatsExport_json_today_1.snap is excluded by !**/*.snap
📒 Files selected for processing (7)
  • cmd/root.go
  • internal/types/types.go
  • internal/ui/export.go
  • internal/ui/log.go
  • internal/ui/report.go
  • internal/ui/stats.go
  • tests/cli/export_test.go

Comment thread internal/ui/export.go
@dhth

dhth commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Thanks for sending in the PR :)
I’ve been a bit occupied lately, but I’ll take a look as soon as I can.

- Flush CSV writers before checking errors to avoid missing write failures
- Consolidate duplicate exportAggEntry/exportStatsEntry converters
- Reject conflicting --plain and --format json/csv flag combinations
- Extract repeated CLI flag literals in export tests

Co-authored-by: Fabricio Reche <fabricioereche@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants