Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions .surface
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,10 @@ ARG basecamp templates delete 00 <id>
ARG basecamp templates show 00 <id>
ARG basecamp templates update 00 <id>
ARG basecamp timeline 00 [me]
ARG basecamp timesheet entry create 00 <recording-id|url>
ARG basecamp timesheet entry delete 00 <id|url>
ARG basecamp timesheet entry show 00 <id|url>
ARG basecamp timesheet entry update 00 <id|url>
ARG basecamp timesheet item 00 <id>
ARG basecamp timesheet recording 00 <id>
ARG basecamp tlgroup copy 00 <id|url>
Expand Down Expand Up @@ -1169,6 +1173,11 @@ CMD basecamp templates show
CMD basecamp templates update
CMD basecamp timeline
CMD basecamp timesheet
CMD basecamp timesheet entry
CMD basecamp timesheet entry create
CMD basecamp timesheet entry delete
CMD basecamp timesheet entry show
CMD basecamp timesheet entry update
CMD basecamp timesheet item
CMD basecamp timesheet project
CMD basecamp timesheet recording
Expand Down Expand Up @@ -15734,6 +15743,143 @@ FLAG basecamp timesheet --styled type=bool
FLAG basecamp timesheet --to type=string
FLAG basecamp timesheet --todolist type=string
FLAG basecamp timesheet --verbose type=count
FLAG basecamp timesheet entry --account type=string
FLAG basecamp timesheet entry --agent type=bool
FLAG basecamp timesheet entry --cache-dir type=string
FLAG basecamp timesheet entry --count type=bool
FLAG basecamp timesheet entry --end type=string
FLAG basecamp timesheet entry --from type=string
FLAG basecamp timesheet entry --help type=bool
FLAG basecamp timesheet entry --hints type=bool
FLAG basecamp timesheet entry --ids-only type=bool
FLAG basecamp timesheet entry --in type=string
FLAG basecamp timesheet entry --jq type=string
FLAG basecamp timesheet entry --json type=bool
FLAG basecamp timesheet entry --markdown type=bool
FLAG basecamp timesheet entry --md type=bool
FLAG basecamp timesheet entry --no-hints type=bool
FLAG basecamp timesheet entry --no-stats type=bool
FLAG basecamp timesheet entry --person type=string
FLAG basecamp timesheet entry --profile type=string
FLAG basecamp timesheet entry --project type=string
FLAG basecamp timesheet entry --quiet type=bool
FLAG basecamp timesheet entry --start type=string
FLAG basecamp timesheet entry --stats type=bool
FLAG basecamp timesheet entry --styled type=bool
FLAG basecamp timesheet entry --to type=string
FLAG basecamp timesheet entry --todolist type=string
FLAG basecamp timesheet entry --verbose type=count
FLAG basecamp timesheet entry create --account type=string
FLAG basecamp timesheet entry create --agent type=bool
FLAG basecamp timesheet entry create --cache-dir type=string
FLAG basecamp timesheet entry create --count type=bool
FLAG basecamp timesheet entry create --date type=string
FLAG basecamp timesheet entry create --description type=string
FLAG basecamp timesheet entry create --end type=string
FLAG basecamp timesheet entry create --from type=string
FLAG basecamp timesheet entry create --help type=bool
FLAG basecamp timesheet entry create --hints type=bool
FLAG basecamp timesheet entry create --hours type=string
FLAG basecamp timesheet entry create --ids-only type=bool
FLAG basecamp timesheet entry create --in type=string
FLAG basecamp timesheet entry create --jq type=string
FLAG basecamp timesheet entry create --json type=bool
FLAG basecamp timesheet entry create --markdown type=bool
FLAG basecamp timesheet entry create --md type=bool
FLAG basecamp timesheet entry create --no-hints type=bool
FLAG basecamp timesheet entry create --no-stats type=bool
FLAG basecamp timesheet entry create --person type=string
FLAG basecamp timesheet entry create --profile type=string
FLAG basecamp timesheet entry create --project type=string
FLAG basecamp timesheet entry create --quiet type=bool
FLAG basecamp timesheet entry create --start type=string
FLAG basecamp timesheet entry create --stats type=bool
FLAG basecamp timesheet entry create --styled type=bool
FLAG basecamp timesheet entry create --to type=string
FLAG basecamp timesheet entry create --todolist type=string
FLAG basecamp timesheet entry create --verbose type=count
FLAG basecamp timesheet entry delete --account type=string
FLAG basecamp timesheet entry delete --agent type=bool
FLAG basecamp timesheet entry delete --cache-dir type=string
FLAG basecamp timesheet entry delete --count type=bool
FLAG basecamp timesheet entry delete --end type=string
FLAG basecamp timesheet entry delete --force type=bool
FLAG basecamp timesheet entry delete --from type=string
FLAG basecamp timesheet entry delete --help type=bool
FLAG basecamp timesheet entry delete --hints type=bool
FLAG basecamp timesheet entry delete --ids-only type=bool
FLAG basecamp timesheet entry delete --in type=string
FLAG basecamp timesheet entry delete --jq type=string
FLAG basecamp timesheet entry delete --json type=bool
FLAG basecamp timesheet entry delete --markdown type=bool
FLAG basecamp timesheet entry delete --md type=bool
FLAG basecamp timesheet entry delete --no-hints type=bool
FLAG basecamp timesheet entry delete --no-stats type=bool
FLAG basecamp timesheet entry delete --person type=string
FLAG basecamp timesheet entry delete --profile type=string
FLAG basecamp timesheet entry delete --project type=string
FLAG basecamp timesheet entry delete --quiet type=bool
FLAG basecamp timesheet entry delete --start type=string
FLAG basecamp timesheet entry delete --stats type=bool
FLAG basecamp timesheet entry delete --styled type=bool
FLAG basecamp timesheet entry delete --to type=string
FLAG basecamp timesheet entry delete --todolist type=string
FLAG basecamp timesheet entry delete --verbose type=count
FLAG basecamp timesheet entry show --account type=string
FLAG basecamp timesheet entry show --agent type=bool
FLAG basecamp timesheet entry show --cache-dir type=string
FLAG basecamp timesheet entry show --count type=bool
FLAG basecamp timesheet entry show --end type=string
FLAG basecamp timesheet entry show --from type=string
FLAG basecamp timesheet entry show --help type=bool
FLAG basecamp timesheet entry show --hints type=bool
FLAG basecamp timesheet entry show --ids-only type=bool
FLAG basecamp timesheet entry show --in type=string
FLAG basecamp timesheet entry show --jq type=string
FLAG basecamp timesheet entry show --json type=bool
FLAG basecamp timesheet entry show --markdown type=bool
FLAG basecamp timesheet entry show --md type=bool
FLAG basecamp timesheet entry show --no-hints type=bool
FLAG basecamp timesheet entry show --no-stats type=bool
FLAG basecamp timesheet entry show --person type=string
FLAG basecamp timesheet entry show --profile type=string
FLAG basecamp timesheet entry show --project type=string
FLAG basecamp timesheet entry show --quiet type=bool
FLAG basecamp timesheet entry show --start type=string
FLAG basecamp timesheet entry show --stats type=bool
FLAG basecamp timesheet entry show --styled type=bool
FLAG basecamp timesheet entry show --to type=string
FLAG basecamp timesheet entry show --todolist type=string
FLAG basecamp timesheet entry show --verbose type=count
FLAG basecamp timesheet entry update --account type=string
FLAG basecamp timesheet entry update --agent type=bool
FLAG basecamp timesheet entry update --cache-dir type=string
FLAG basecamp timesheet entry update --count type=bool
FLAG basecamp timesheet entry update --date type=string
FLAG basecamp timesheet entry update --description type=string
FLAG basecamp timesheet entry update --end type=string
FLAG basecamp timesheet entry update --from type=string
FLAG basecamp timesheet entry update --help type=bool
FLAG basecamp timesheet entry update --hints type=bool
FLAG basecamp timesheet entry update --hours type=string
FLAG basecamp timesheet entry update --ids-only type=bool
FLAG basecamp timesheet entry update --in type=string
FLAG basecamp timesheet entry update --jq type=string
FLAG basecamp timesheet entry update --json type=bool
FLAG basecamp timesheet entry update --markdown type=bool
FLAG basecamp timesheet entry update --md type=bool
FLAG basecamp timesheet entry update --no-hints type=bool
FLAG basecamp timesheet entry update --no-stats type=bool
FLAG basecamp timesheet entry update --person type=string
FLAG basecamp timesheet entry update --profile type=string
FLAG basecamp timesheet entry update --project type=string
FLAG basecamp timesheet entry update --quiet type=bool
FLAG basecamp timesheet entry update --start type=string
FLAG basecamp timesheet entry update --stats type=bool
FLAG basecamp timesheet entry update --styled type=bool
FLAG basecamp timesheet entry update --to type=string
FLAG basecamp timesheet entry update --todolist type=string
FLAG basecamp timesheet entry update --verbose type=count
FLAG basecamp timesheet item --account type=string
FLAG basecamp timesheet item --agent type=bool
FLAG basecamp timesheet item --cache-dir type=string
Expand Down Expand Up @@ -21224,6 +21370,11 @@ SUB basecamp templates show
SUB basecamp templates update
SUB basecamp timeline
SUB basecamp timesheet
SUB basecamp timesheet entry
SUB basecamp timesheet entry create
SUB basecamp timesheet entry delete
SUB basecamp timesheet entry show
SUB basecamp timesheet entry update
SUB basecamp timesheet item
SUB basecamp timesheet project
SUB basecamp timesheet recording
Expand Down
2 changes: 1 addition & 1 deletion API-COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ cannot faithfully cover at least one endpoint for a reason outside the CLI. A
| **Templates** |
| templates | 10 | `templates` | ✅ | BC4 | - | list, show, create, update, delete, construct, construction, library, copy, copy-status |
| **Time Tracking** |
| timesheets | 6 | `timesheet` | ✅ | BC4 | - | list, show, create, update, delete |
| timesheets | 7 | `timesheet` | ✅ | BC4 | - | Reports: account-wide (`timesheet report`), project (`timesheet project`), item (`timesheet item`). Entries: `timesheet entry show`, `create` (on an item), `update`, `delete` (permanent: bc3 refuses to trash or archive an entry) |
| **Subscriptions** |
| subscriptions | 4 | `subscriptions` | ✅ | BC4 | - | show, subscribe, unsubscribe, add/remove |
| **Check-ins (Automatic)** |
Expand Down
4 changes: 4 additions & 0 deletions e2e/smoke/.qa-allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ test_reports_schedule_returns_schedule_entries # 400 on some environments — sm
test_timesheet_report_returns_timesheet_data # timesheets not enabled — smoke
test_timesheet_project_returns_project_timesheet # timesheets not enabled — smoke
test_timesheet_item_returns_timesheet_item_detail # timesheets not enabled — smoke
test_timesheet_entry_create_logs_time_against_a_todo # timesheets not enabled — smoke
test_timesheet_entry_show_returns_entry_detail # depends on timesheet entry create — smoke
test_timesheet_entry_update_changes_the_hours # depends on timesheet entry create — smoke
test_timesheet_entry_delete_removes_the_entry # depends on timesheet entry create — smoke

# Lineup API may not exist on all environments
test_lineup_create_creates_a_lineup_marker # API not available on some servers — smoke
Expand Down
1 change: 1 addition & 0 deletions e2e/smoke/run_smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ echo "--- Level 1: Mutation tests (parallel, $jobs jobs) ---"
level1=(
"$SMOKE_DIR"/smoke_todos_write.bats
"$SMOKE_DIR"/smoke_subtasks.bats
"$SMOKE_DIR"/smoke_timesheet_write.bats
"$SMOKE_DIR"/smoke_messages_write.bats
"$SMOKE_DIR"/smoke_files_write.bats
"$SMOKE_DIR"/smoke_cards_write.bats
Expand Down
57 changes: 57 additions & 0 deletions e2e/smoke/smoke_timesheet_write.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bats
# smoke_timesheet_write.bats - Level 1: Timesheet entry lifecycle on a to-do

load smoke_helper

setup_file() {
# Each test depends on the one before it: create writes the entry id the
# rest read, and delete must come last.
export BATS_NO_PARALLELIZE_WITHIN_FILE=true
ensure_token || return 1
ensure_todo || return 1
}

@test "timesheet entry create logs time against a todo" {
run_smoke basecamp timesheet entry create "$QA_TODO" --hours 0.25 --description "Smoke entry $(date +%s)" --json
# Timesheets may not be enabled on all accounts (403 forbidden)
[[ "$status" -eq 4 ]] && mark_unverifiable "Timesheets not enabled in this account"
Comment thread
jeremy marked this conversation as resolved.
Resolved
assert_success
assert_json_value '.ok' 'true'
assert_json_not_null '.data.id'

echo "$output" | jq -r '.data.id' > "$BATS_FILE_TMPDIR/timesheet_entry_id"
}

@test "timesheet entry show returns entry detail" {
local id_file="$BATS_FILE_TMPDIR/timesheet_entry_id"
[[ -f "$id_file" ]] || mark_unverifiable "No timesheet entry created in prior test"
Comment thread
jeremy marked this conversation as resolved.
Resolved
local entry_id
entry_id=$(<"$id_file")

run_smoke basecamp timesheet entry show "$entry_id" --json
assert_success
assert_json_value '.ok' 'true'
assert_json_value '.data.parent.id' "$QA_TODO"
}

@test "timesheet entry update changes the hours" {
local id_file="$BATS_FILE_TMPDIR/timesheet_entry_id"
[[ -f "$id_file" ]] || mark_unverifiable "No timesheet entry created in prior test"
local entry_id
entry_id=$(<"$id_file")

run_smoke basecamp timesheet entry update "$entry_id" --hours 0:30 --json
assert_success
assert_json_value '.ok' 'true'
}

@test "timesheet entry delete removes the entry" {
local id_file="$BATS_FILE_TMPDIR/timesheet_entry_id"
[[ -f "$id_file" ]] || mark_unverifiable "No timesheet entry created in prior test"
local entry_id
entry_id=$(<"$id_file")

run_smoke basecamp timesheet entry delete "$entry_id" --force --json
assert_success
assert_json_value '.data.deleted' 'true'
}
39 changes: 39 additions & 0 deletions e2e/timesheet.bats
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,42 @@ load test_helper
run basecamp timesheet foobar
# Command may show help or require project - just verify it runs
}


# Entry verbs: refused locally, before any request

@test "timesheet entry create without --hours shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp timesheet entry create 789
assert_failure
assert_output_contains "--hours is required"
}

@test "timesheet entry update with nothing to change shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp timesheet entry update 123
assert_failure
assert_output_contains "Nothing to update"
}

@test "timesheet entry delete refuses an item URL" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp timesheet entry delete "https://3.basecamp.com/99999/buckets/89/todos/789" --force
assert_failure
assert_output_contains "does not name a timesheet entry"
}

@test "timesheet entry delete needs --force without a terminal" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp timesheet entry delete 123 --json
assert_failure
assert_output_contains "--force"
}
2 changes: 1 addition & 1 deletion internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func CommandCategories() []CommandCategory {
{
Name: "Scheduling & Time",
Commands: []CommandInfo{
{Name: "timesheet", Category: "scheduling", Description: "Manage time tracking", Actions: []string{"report", "project", "item"}},
{Name: "timesheet", Category: "scheduling", Description: "Manage time tracking", Actions: []string{"report", "project", "item", "entry"}},
{Name: "timeline", Category: "scheduling", Description: "View activity timelines", Actions: []string{}},
{Name: "reports", Category: "scheduling", Description: "View reports", Actions: []string{"assignable", "assigned", "overdue", "schedule"}},
{Name: "assignments", Category: "scheduling", Description: "View my assignments", Actions: []string{"list", "completed", "due", "prioritize", "deprioritize", "reorder"}},
Expand Down
6 changes: 4 additions & 2 deletions internal/commands/timesheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ func NewTimesheetCmd() *cobra.Command {

Timesheet entries track time logged against any item (todo, message,
document, etc.). The account-wide report defaults to the last month if no
date range is specified.`,
Annotations: map[string]string{"agent_notes": "Time is logged against items (todos, cards, messages, etc.)\nbasecamp clock is a shortcut for timesheet entry create\nUse basecamp reports assigned --json to see assigned items, then clock time against them"},
date range is specified. "timesheet entry" logs, shows, changes and deletes
one entry.`,
Annotations: map[string]string{"agent_notes": "Time is logged against items (todos, cards, messages, etc.)\nbasecamp timesheet entry create <item-id> --hours 1.5 logs time; show, update and delete take the entry id\nUse basecamp reports assigned --json to see assigned items, then log time against them"},
RunE: func(cmd *cobra.Command, args []string) error {
return runTimesheetReport(cmd, startDate, endDate, personID)
},
Expand All @@ -44,6 +45,7 @@ date range is specified.`,
newTimesheetReportCmd(&startDate, &endDate, &personID),
newTimesheetProjectCmd(&project),
newTimesheetRecordingCmd(&project),
newTimesheetEntryCmd(),
)

return cmd
Expand Down
Loading
Loading