feat(12.1-01): declared record actions with an applicability rule
- pact.HasAdminRecordActions with AdminRecordAction (Applies, Run)
- config_form.yaml recordActions, compiled fail-loud
- show response meta.actions lists the permitted actions that apply
- POST .../{controller}/{id}/actions/{action}: record loaded and locked
through the form scope; 404 out of scope, 409 when it does not apply
- RecordActions.vue with confirm and request flow (mounted by plan 02)
- roster fixture, smoke tests, OpenAPI, TS types, READMEs, docs
This commit is contained in:
@@ -279,6 +279,18 @@ func TestPhase10OpenAPIConformance(t *testing.T) {
|
||||
spare := e.send(t, http.MethodPost, "/acme/conform/gadgets", map[string]any{"name": "spare-" + e.stamp}, true)
|
||||
return e.send(t, http.MethodPost, "/acme/conform/gadgets/bulk-delete", map[string]any{"ids": []uint{dataID(t, spare.Body.Bytes())}}, true)
|
||||
}, into[cabana.Envelope[cabana.BulkResult]](), nil},
|
||||
{"POST /{vendor}/{plugin}/{controller}/{id}/actions/{action}", 200, "cabana.Envelope-cabana_AdminActionResult", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
shown := e.send(t, http.MethodGet, fmt.Sprintf("/acme/conform/gadgets/%d", e.gadgetID), nil, true)
|
||||
if !strings.Contains(shown.Body.String(), `"actions":[{"name":"ping","label":"Ping"}]`) {
|
||||
t.Fatalf("show does not offer the record action: %s", shown.Body.String())
|
||||
}
|
||||
rec := e.send(t, http.MethodPost, fmt.Sprintf("/acme/conform/gadgets/%d/actions/ping", e.gadgetID), map[string]any{}, true)
|
||||
var body cabana.Envelope[cabana.AdminActionResult]
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil || body.Data.Message != "Pinged" || body.Data.Fill == nil || len(body.Data.Fill) != 0 {
|
||||
t.Fatalf("record action body = %s (%v)", rec.Body.String(), err)
|
||||
}
|
||||
return rec
|
||||
}, into[cabana.Envelope[cabana.AdminActionResult]](), nil},
|
||||
{"POST /{vendor}/{plugin}/{controller}/bulk/{action}", 200, "cabana.Envelope-cabana_BulkActionResult", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
rec := e.send(t, http.MethodPost, "/acme/conform/gadgets/bulk/touch", map[string]any{"ids": []uint{e.gadgetID}}, true)
|
||||
var body cabana.Envelope[cabana.BulkActionResult]
|
||||
@@ -808,6 +820,17 @@ func (conformController) AdminBulkActions() []pact.AdminBulkAction {
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// AdminRecordActions registers the record action the form's recordActions
|
||||
// declares.
|
||||
func (conformController) AdminRecordActions() []pact.AdminRecordAction {
|
||||
return []pact.AdminRecordAction{{
|
||||
Name: "ping", Label: "Ping", Permissions: []string{"acme.conform.access"},
|
||||
Run: func(context.Context, pact.AdminRecordActionInput) (pact.AdminRecordActionResult, error) {
|
||||
return pact.AdminRecordActionResult{Message: "Pinged"}, nil
|
||||
},
|
||||
}}
|
||||
}
|
||||
func (conformController) AdminJS() []string { return []string{"assets/js/lookup.js"} }
|
||||
|
||||
// PartialData supplies curated view models, never the gadget model itself.
|
||||
@@ -892,6 +915,7 @@ create:
|
||||
update:
|
||||
redirect: acme/conform/gadgets
|
||||
redirectClose: acme/conform/gadgets
|
||||
recordActions: [ping]
|
||||
`),
|
||||
"controllers/gadgets/config_relation.yaml": file(`members:
|
||||
label: Members
|
||||
|
||||
Reference in New Issue
Block a user