Skip to content

Commit 1f5160a

Browse files
committed
feat(bigquery): add dashboard management UI
1 parent cd9f05c commit 1f5160a

19 files changed

Lines changed: 1860 additions & 88 deletions

File tree

.agents/orbit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
| 2026-05-05 | Orbit | split DynamoDB dashboard management UI tasks into a Codex autoloop | `scripts/dynamodb-dashboard-management-autoloop/`, `.gitignore` | ready for guarded operations UI, Query/Scan UI, E2E, docs, and full-management-ui gates |
1616
| 2026-05-06 | Orbit | split DynamoDB dashboard advanced UI tasks into a Codex autoloop | `scripts/dynamodb-dashboard-advanced-autoloop/`, `.gitignore` | ready for pagination, saved/recent operations, table wizard, validation, delete confirmation, e2e, docs, and full-advanced-ui gates |
1717
| 2026-05-06 | Orbit | split GCS React dashboard integration into a Codex autoloop | `scripts/gcs-dashboard-react-autoloop/`, `.gitignore` | ready for React route, GCS inspection, guarded management, e2e, docs, and full-react-gcs gates |
18+
| 2026-05-06 | Orbit | split BigQuery dashboard management UI into a Codex autoloop | `scripts/bigquery-dashboard-management-autoloop/`, `.gitignore` | ready for query runner, dataset/table creation, row insert, job detail, validation, e2e, docs, and full-management-ui gates |

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ scripts/bigquery-autoloop/progress.md
7878
scripts/bigquery-autoloop/done.md
7979
scripts/bigquery-autoloop/iteration-*.out
8080
scripts/bigquery-autoloop/prompt-*.*
81+
scripts/bigquery-dashboard-management-autoloop/.run-loop.lock
82+
scripts/bigquery-dashboard-management-autoloop/.circuit-state
83+
scripts/bigquery-dashboard-management-autoloop/runner.jsonl
84+
scripts/bigquery-dashboard-management-autoloop/runner.log
85+
scripts/bigquery-dashboard-management-autoloop/state.env
86+
scripts/bigquery-dashboard-management-autoloop/state.env.sha256
87+
scripts/bigquery-dashboard-management-autoloop/progress.md
88+
scripts/bigquery-dashboard-management-autoloop/done.md
89+
scripts/bigquery-dashboard-management-autoloop/iteration-*.out
90+
scripts/bigquery-dashboard-management-autoloop/verify-*.out
91+
scripts/bigquery-dashboard-management-autoloop/prompt-*.*
8192
scripts/sqs-autoloop/.run-loop.lock
8293
scripts/sqs-autoloop/.circuit-state
8394
scripts/sqs-autoloop/runner.jsonl

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ go run ./cmd/devcloud dashboard
4545
go run ./cmd/devcloud reset
4646
```
4747

48+
## BigQuery Dashboard Management
49+
50+
`/dashboard/bigquery` includes a compact local management console for BigQuery development workflows. It keeps the existing catalog browser for projects, datasets, tables, rows, schemas, and jobs, and adds a SQL query runner with `useLegacySql=false`, dry run, max results, result table, selected result JSON, and job reference.
51+
52+
The dashboard can create local datasets and tables and insert local table rows through guarded `datasets.insert`, `tables.insert`, and `tabledata.insertAll` flows. Guided forms cover common fields, raw JSON mode is available for request-shape testing, and the row editor validates JSON before calling insertAll while showing partial insert errors.
53+
54+
Safety boundaries: dashboard mutations go through `/api/bigquery/*` or the local BigQuery REST API path, never direct storage calls. The UI does not persist or log row payloads, credentials, Authorization headers, bearer tokens, or full request bodies. When BigQuery is disabled, query and mutation controls remain unavailable.
55+
4856
## Configuration
4957

5058
Configuration lives at `.devcloud/config.yaml`. Runtime data is stored under `.devcloud/data` by default.

docs/design-bigquery-compat.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -706,17 +706,29 @@ GET /api/bigquery/projects/{project}/datasets/{dataset}/tables/{table}/rows?limi
706706
GET /api/bigquery/projects/{project}/jobs
707707
GET /api/bigquery/projects/{project}/jobs/{job}
708708
POST /api/bigquery/projects/{project}/queries
709+
POST /api/bigquery/projects/{project}/datasets
710+
POST /api/bigquery/projects/{project}/datasets/{dataset}/tables
711+
POST /api/bigquery/projects/{project}/datasets/{dataset}/tables/{table}/insertAll
709712
```
710713

711714
### UI Surface
712715

713716
- Project/dataset/table navigator.
714717
- Schema inspector.
715718
- Row preview table.
716-
- Job list and job detail.
717-
- Query editor for local GoogleSQL subset.
718-
- Query results table.
719+
- SQL query runner for the local GoogleSQL subset with `useLegacySql=false`, dry run, max results, result table, selected result JSON, and job reference.
720+
- Guarded BigQuery dashboard management controls for `datasets.insert`, `tables.insert`, and `tabledata.insertAll`.
721+
- Guided create dataset/table fields plus raw JSON mode for local request shapes.
722+
- Insert row JSON editor with client-side validation and partial insert error display.
723+
- Job list, recent query metadata, selected job JSON, and job detail.
719724
- Error panel showing BigQuery-style `reason`, `location`, and `message`.
725+
- Disabled BigQuery state keeps mutation and query controls unavailable.
726+
727+
Dashboard safety boundaries:
728+
729+
- All mutations go through `/api/bigquery/*` or the local BigQuery REST API path; dashboard code does not call storage directly.
730+
- Row payloads, credentials, Authorization headers, bearer tokens, and full request bodies are not persisted or logged by dashboard management flows.
731+
- Selected job JSON shown in the dashboard redacts query parameter values from the UI detail view.
720732

721733
## Implementation Plan
722734

internal/dashboard/assets/react/assets/index-BGtXVHIp.js

Lines changed: 96 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/dashboard/assets/react/assets/index-CF9eU0ES.js

Lines changed: 0 additions & 76 deletions
This file was deleted.

internal/dashboard/assets/react/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>devcloud Dashboard</title>
7-
<script type="module" crossorigin src="/dashboard/assets/index-CF9eU0ES.js"></script>
7+
<script type="module" crossorigin src="/dashboard/assets/index-BGtXVHIp.js"></script>
88
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-CFkAsiRY.css">
99
</head>
1010
<body>

internal/dashboard/server.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,19 @@ func (s *Server) handleBigQueryProjectResource(w http.ResponseWriter, r *http.Re
436436
methodNotAllowed(w, "POST")
437437
return
438438
}
439-
s.forwardBigQueryQuery(w, r, projectID)
439+
s.forwardBigQueryRequest(w, r, "/bigquery/v2/projects/"+url.PathEscape(projectID)+"/queries")
440+
return
441+
}
442+
if len(parts) == 2 && parts[1] == "datasets" && r.Method == http.MethodPost {
443+
s.forwardBigQueryRequest(w, r, "/bigquery/v2/projects/"+url.PathEscape(projectID)+"/datasets")
444+
return
445+
}
446+
if len(parts) == 4 && parts[1] == "datasets" && parts[3] == "tables" && r.Method == http.MethodPost {
447+
s.forwardBigQueryRequest(w, r, "/bigquery/v2/projects/"+url.PathEscape(projectID)+"/datasets/"+url.PathEscape(parts[2])+"/tables")
448+
return
449+
}
450+
if len(parts) == 6 && parts[1] == "datasets" && parts[3] == "tables" && parts[5] == "insertAll" && r.Method == http.MethodPost {
451+
s.forwardBigQueryRequest(w, r, "/bigquery/v2/projects/"+url.PathEscape(projectID)+"/datasets/"+url.PathEscape(parts[2])+"/tables/"+url.PathEscape(parts[4])+"/insertAll")
440452
return
441453
}
442454
if r.Method != http.MethodGet {
@@ -502,14 +514,13 @@ func (s *Server) handleBigQueryProjectResource(w http.ResponseWriter, r *http.Re
502514
}
503515
}
504516

505-
func (s *Server) forwardBigQueryQuery(w http.ResponseWriter, r *http.Request, projectID string) {
506-
queryURL := &url.URL{
507-
Path: "/bigquery/v2/projects/" + url.PathEscape(projectID) + "/queries",
517+
func (s *Server) forwardBigQueryRequest(w http.ResponseWriter, r *http.Request, path string) {
518+
forwardURL := &url.URL{
519+
Path: path,
508520
RawQuery: r.URL.RawQuery,
509521
}
510522
req := r.Clone(r.Context())
511-
req.Method = http.MethodPost
512-
req.URL = queryURL
523+
req.URL = forwardURL
513524
req.RequestURI = ""
514525
req.Body = r.Body
515526
req.Header = r.Header.Clone()

internal/dashboard/server_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,13 +1225,55 @@ func TestBigQueryDashboardPageAndAPIExposeCatalog(t *testing.T) {
12251225
if jobs.Code != http.StatusOK || !strings.Contains(jobs.Body.String(), `"state":"DONE"`) {
12261226
t.Fatalf("BigQuery jobs = %d body=%s", jobs.Code, jobs.Body.String())
12271227
}
1228+
jobDetail := performRequest(routes, http.MethodGet, "/api/bigquery/projects/devcloud/jobs/devcloud_query_")
1229+
if jobDetail.Code != http.StatusNotFound {
1230+
t.Fatalf("BigQuery unknown job detail = %d body=%s", jobDetail.Code, jobDetail.Body.String())
1231+
}
12281232
query := performRequestWithBody(routes, http.MethodPost, "/api/bigquery/projects/devcloud/queries", `{
12291233
"query":"SELECT id, name FROM `+"`devcloud.analytics.people`"+` WHERE age >= 30",
12301234
"useLegacySql":false
12311235
}`)
12321236
if query.Code != http.StatusOK || !strings.Contains(query.Body.String(), `"kind":"bigquery#queryResponse"`) || !strings.Contains(query.Body.String(), `"totalRows":"1"`) {
12331237
t.Fatalf("BigQuery dashboard query = %d body=%s", query.Code, query.Body.String())
12341238
}
1239+
var queryPayload struct {
1240+
JobReference struct {
1241+
JobID string `json:"jobId"`
1242+
} `json:"jobReference"`
1243+
}
1244+
if err := json.Unmarshal(query.Body.Bytes(), &queryPayload); err != nil {
1245+
t.Fatalf("decode BigQuery dashboard query response: %v", err)
1246+
}
1247+
queryJobDetail := performRequest(routes, http.MethodGet, "/api/bigquery/projects/devcloud/jobs/"+queryPayload.JobReference.JobID)
1248+
if queryJobDetail.Code != http.StatusOK || !strings.Contains(queryJobDetail.Body.String(), `"jobId":"`+queryPayload.JobReference.JobID+`"`) || !strings.Contains(queryJobDetail.Body.String(), `"state":"DONE"`) {
1249+
t.Fatalf("BigQuery query job detail = %d body=%s", queryJobDetail.Code, queryJobDetail.Body.String())
1250+
}
1251+
managementDataset := performRequestWithBody(routes, http.MethodPost, "/api/bigquery/projects/devcloud/datasets", `{
1252+
"datasetReference":{"datasetId":"dashboard_ops"},
1253+
"location":"US",
1254+
"friendlyName":"Dashboard Ops"
1255+
}`)
1256+
if managementDataset.Code != http.StatusOK || !strings.Contains(managementDataset.Body.String(), `"datasetId":"dashboard_ops"`) {
1257+
t.Fatalf("BigQuery dashboard dataset create = %d body=%s", managementDataset.Code, managementDataset.Body.String())
1258+
}
1259+
managementTable := performRequestWithBody(routes, http.MethodPost, "/api/bigquery/projects/devcloud/datasets/dashboard_ops/tables", `{
1260+
"tableReference":{"tableId":"events"},
1261+
"schema":{"fields":[{"name":"event_id","type":"STRING","mode":"REQUIRED"},{"name":"count","type":"INTEGER"}]}
1262+
}`)
1263+
if managementTable.Code != http.StatusOK || !strings.Contains(managementTable.Body.String(), `"tableId":"events"`) {
1264+
t.Fatalf("BigQuery dashboard table create = %d body=%s", managementTable.Code, managementTable.Body.String())
1265+
}
1266+
managementInsert := performRequestWithBody(routes, http.MethodPost, "/api/bigquery/projects/devcloud/datasets/dashboard_ops/tables/events/insertAll", `{
1267+
"skipInvalidRows":true,
1268+
"rows":[{"insertId":"event-1","json":{"event_id":"signup","count":1}}]
1269+
}`)
1270+
if managementInsert.Code != http.StatusOK || !strings.Contains(managementInsert.Body.String(), `"kind":"bigquery#tableDataInsertAllResponse"`) {
1271+
t.Fatalf("BigQuery dashboard insertAll = %d body=%s", managementInsert.Code, managementInsert.Body.String())
1272+
}
1273+
managementRows := performRequest(routes, http.MethodGet, "/api/bigquery/projects/devcloud/datasets/dashboard_ops/tables/events/rows?limit=1")
1274+
if managementRows.Code != http.StatusOK || !strings.Contains(managementRows.Body.String(), `"event_id":"signup"`) {
1275+
t.Fatalf("BigQuery dashboard inserted rows = %d body=%s", managementRows.Code, managementRows.Body.String())
1276+
}
12351277
}
12361278

12371279
func reactAssetPath(t *testing.T, indexHTML string) string {

scripts/bigquery-autoloop/verify.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ services:
158158
enabled: true
159159
project: ${PROJECT}
160160
location: ${LOCATION}
161+
redshift:
162+
enabled: false
163+
sqs:
164+
enabled: false
165+
pubsub:
166+
enabled: false
161167
EOF
162168

163169
run_check "devcloud binary builds" go build -o "${TMP_DIR}/devcloud" ./cmd/devcloud

0 commit comments

Comments
 (0)