carlos analytics: count_from, one PUT for both set flags, gofmt
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 files changed,
+103
−21
analytics_cmd.go+32 −15analytics_cmd_test.go+70 −6apimode.go+1 −0
diff --git a/analytics_cmd.go b/analytics_cmd.go| index f7c1f2d5d..27f781de9 100644 |
| --- a/analytics_cmd.go |
| +++ b/analytics_cmd.go |
| @@ -38,25 +38,23 @@ func runAnalytics(args []string) { |
| app2 := resolvedApp(sess, acct, *app, analyticsUsage) |
| ctx := context.Background() |
| - if *setCounting != "" { |
| - if *setCounting != "edge" && *setCounting != "off" { |
| - log.Fatalf("analytics: --set-count wants edge or off") |
| + if *setCounting != "" || *setDoor != "" { |
| + body, err := analyticsSetBody(*setCounting, *setDoor) |
| + if err != nil { |
| + log.Fatalf("analytics: %v", err) |
| } |
| - if err := sess.telemetryPut(ctx, acct, app2, map[string]any{"analytics": map[string]string{"count": *setCounting}}); err != nil { |
| + if err := sess.telemetryPut(ctx, acct, app2, map[string]any{"analytics": body}); err != nil { |
| log.Fatalf("analytics: %v", err) |
| } |
| - fmt.Fprintf(stdout, "Analytics counting for %s is now %s.\n", app2, *setCounting) |
| - return |
| - } |
| - |
| - if *setDoor != "" { |
| - if *setDoor != "on" && *setDoor != "off" { |
| - log.Fatalf("analytics: --set-door wants on or off") |
| + // Build summary of what was set |
| + var parts []string |
| + if *setCounting != "" { |
| + parts = append(parts, fmt.Sprintf("counting %s", *setCounting)) |
| } |
| - if err := sess.telemetryPut(ctx, acct, app2, map[string]any{"analytics": map[string]string{"door": *setDoor}}); err != nil { |
| - log.Fatalf("analytics: %v", err) |
| + if *setDoor != "" { |
| + parts = append(parts, fmt.Sprintf("door %s", *setDoor)) |
| } |
| - fmt.Fprintf(stdout, "Analytics door for %s is now %s.\n", app2, *setDoor) |
| + fmt.Fprintf(stdout, "Analytics for %s: %s.\n", app2, joinOr(parts, "")) |
| return |
| } |
| @@ -67,8 +65,27 @@ func runAnalytics(args []string) { |
| } |
| printAnalytics(stdout, res) |
| if !res.On { |
| - mutedf(stdout, "analytics is off for this app — turn it on with `carlos analytics --app %s --set-count edge`", app2) |
| + mutedf(stdout, "analytics is off for this app (from the %s) — turn it on with `carlos analytics --app %s --set-count edge`", res.CountFrom, app2) |
| + } |
| +} |
| + |
| +// analyticsSetBody builds the analytics settings map from the given flag values, |
| +// validating them first. Returns error for invalid values. |
| +func analyticsSetBody(count, door string) (map[string]string, error) { |
| + if count != "" && count != "edge" && count != "off" { |
| + return nil, fmt.Errorf("--set-count wants edge or off") |
| + } |
| + if door != "" && door != "on" && door != "off" { |
| + return nil, fmt.Errorf("--set-door wants on or off") |
| + } |
| + body := make(map[string]string) |
| + if count != "" { |
| + body["count"] = count |
| + } |
| + if door != "" { |
| + body["door"] = door |
| } |
| + return body, nil |
| } |
| // printAnalytics renders the analytics summary: a headline line with totals, |
diff --git a/analytics_cmd_test.go b/analytics_cmd_test.go| index ac7b9fff7..ada1248b2 100644 |
| --- a/analytics_cmd_test.go |
| +++ b/analytics_cmd_test.go |
| @@ -50,12 +50,12 @@ func TestPrintAnalytics(t *testing.T) { |
| output := buf.String() |
| checks := []string{ |
| - "5", // PV count |
| - "4", // Visitors count |
| - "daily visitors, summed", // text from headline |
| - "/pricing", // page |
| - "news.ycombinator.com", // referer |
| - "signup", // event |
| + "5", // PV count |
| + "4", // Visitors count |
| + "daily visitors, summed", // text from headline |
| + "/pricing", // page |
| + "news.ycombinator.com", // referer |
| + "signup", // event |
| } |
| for _, check := range checks { |
| @@ -64,3 +64,67 @@ func TestPrintAnalytics(t *testing.T) { |
| } |
| } |
| } |
| + |
| +func TestAnalyticsSetFlags(t *testing.T) { |
| + tests := []struct { |
| + name string |
| + count string |
| + door string |
| + wantErr bool |
| + want map[string]string |
| + }{ |
| + { |
| + name: "both flags", |
| + count: "edge", |
| + door: "on", |
| + want: map[string]string{"count": "edge", "door": "on"}, |
| + }, |
| + { |
| + name: "count only", |
| + count: "off", |
| + want: map[string]string{"count": "off"}, |
| + }, |
| + { |
| + name: "door only", |
| + door: "off", |
| + want: map[string]string{"door": "off"}, |
| + }, |
| + { |
| + name: "neither", |
| + want: map[string]string{}, |
| + }, |
| + { |
| + name: "invalid count", |
| + count: "invalid", |
| + wantErr: true, |
| + }, |
| + { |
| + name: "invalid door", |
| + door: "maybe", |
| + wantErr: true, |
| + }, |
| + } |
| + |
| + for _, tt := range tests { |
| + t.Run(tt.name, func(t *testing.T) { |
| + got, err := analyticsSetBody(tt.count, tt.door) |
| + if (err != nil) != tt.wantErr { |
| + t.Errorf("analyticsSetBody() error = %v, wantErr %v", err, tt.wantErr) |
| + return |
| + } |
| + if err != nil { |
| + return |
| + } |
| + if len(got) != len(tt.want) { |
| + t.Errorf("analyticsSetBody() got %v, want %v", got, tt.want) |
| + return |
| + } |
| + for k, v := range tt.want { |
| + if got[k] != v { |
| + t.Errorf("analyticsSetBody() got %v, want %v", got, tt.want) |
| + return |
| + } |
| + } |
| + }) |
| + } |
| +} |
diff --git a/apimode.go b/apimode.go| index 2608f204f..beb5fa821 100644 |
| --- a/apimode.go |
| +++ b/apimode.go |
| @@ -2890,6 +2890,7 @@ type apiAnalyticsResponse struct { |
| To string `json:"to"` |
| On bool `json:"on"` |
| Count string `json:"count"` |
| + CountFrom string `json:"count_from"` |
| PV int64 `json:"pv"` |
| Visitors int64 `json:"visitors"` |
| Bytes int64 `json:"bytes"` |