Skip to content

Commit a8f2742

Browse files
authored
Revitalize admin shell for plugin tools (#35)
* feat: bridge admin tool auth context * feat: render plugin config forms * feat: pass admin grants to embedded tools
1 parent 5fa2ac5 commit a8f2742

5 files changed

Lines changed: 309 additions & 16 deletions

File tree

internal/assets_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ func TestEmbeddedAdminShell(t *testing.T) {
1616
`id="contribution-nav"`,
1717
`id="contribution-list"`,
1818
`Authorization`,
19+
`workflow.admin.auth.request`,
20+
`workflow.admin.auth.response`,
21+
`grantedScopes: adminToolGrants`,
22+
`adminToolFrames`,
23+
`event.origin !== window.location.origin`,
24+
`startsWith('/admin')`,
25+
`render_mode === 'config-form'`,
26+
`renderConfigForm`,
27+
`validate_path`,
28+
`fetchConfigDescription`,
1929
}
2030
for _, needle := range required {
2131
if !strings.Contains(html, needle) {
@@ -34,6 +44,31 @@ func TestEmbeddedAdminShell(t *testing.T) {
3444
}
3545
}
3646

47+
func TestEmbeddedAdminShellUsesUserFacingToolLanguage(t *testing.T) {
48+
html := string(mustReadEmbeddedAsset(t, "ui_dist/index.html"))
49+
required := []string{
50+
"Admin tools",
51+
"Management pages",
52+
}
53+
for _, needle := range required {
54+
if !strings.Contains(html, needle) {
55+
t.Fatalf("embedded admin shell missing user-facing copy %q", needle)
56+
}
57+
}
58+
forbidden := []string{
59+
"Surfaces",
60+
"Registered surfaces",
61+
"Active surface",
62+
"Contributions",
63+
"contributed admin surfaces",
64+
}
65+
for _, needle := range forbidden {
66+
if strings.Contains(html, needle) {
67+
t.Fatalf("embedded admin shell exposes implementation jargon %q", needle)
68+
}
69+
}
70+
}
71+
3772
func TestEmbeddedAdminShellDoesNotHardcodePluginSurfaces(t *testing.T) {
3873
html := string(mustReadEmbeddedAsset(t, "ui_dist/index.html"))
3974
forbidden := []string{

internal/module_dashboard.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sync"
77

88
"github.com/GoCodeAlone/workflow-plugin-admin/internal/contracts"
9+
"google.golang.org/protobuf/types/known/structpb"
910
)
1011

1112
var dashboardModules = struct {
@@ -127,6 +128,11 @@ func contributionFromMap(args map[string]any) *contracts.AdminContribution {
127128
AppContext: stringValue(args, "app_context"),
128129
Actions: stringSliceValue(args, "actions"),
129130
}
131+
if metadata := mapValue(args, "metadata"); len(metadata) > 0 {
132+
if pbMetadata, err := structpb.NewStruct(metadata); err == nil {
133+
contribution.Metadata = pbMetadata
134+
}
135+
}
130136
for _, permission := range permissionValues(args["permissions"]) {
131137
contribution.Permissions = append(contribution.Permissions, permission)
132138
}
@@ -146,9 +152,17 @@ func contributionToMap(contribution *contracts.AdminContribution) map[string]any
146152
"app_context": contribution.AppContext,
147153
"actions": append([]string(nil), contribution.Actions...),
148154
"permissions": permissionMaps(contribution.Permissions),
155+
"metadata": contributionMetadataMap(contribution),
149156
}
150157
}
151158

159+
func contributionMetadataMap(contribution *contracts.AdminContribution) map[string]any {
160+
if contribution == nil || contribution.Metadata == nil {
161+
return map[string]any{}
162+
}
163+
return contribution.Metadata.AsMap()
164+
}
165+
152166
func permissionValues(value any) []*contracts.AdminPermission {
153167
switch permissions := value.(type) {
154168
case []string:
@@ -204,6 +218,23 @@ func stringValue(args map[string]any, key string) string {
204218
return value
205219
}
206220

221+
func mapValue(args map[string]any, key string) map[string]any {
222+
switch value := args[key].(type) {
223+
case map[string]any:
224+
return value
225+
case map[any]any:
226+
out := make(map[string]any, len(value))
227+
for k, v := range value {
228+
if s, ok := k.(string); ok {
229+
out[s] = v
230+
}
231+
}
232+
return out
233+
default:
234+
return nil
235+
}
236+
}
237+
207238
func stringSliceValue(args map[string]any, key string) []string {
208239
switch value := args[key].(type) {
209240
case []string:

internal/module_dashboard_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func TestDashboardModuleServiceInvoker(t *testing.T) {
9898
"category": "operations",
9999
"render_mode": "json-schema",
100100
"app_context": "scenario",
101+
"metadata": map[string]any{
102+
"describe_path": "/api/admin/orders/config",
103+
"validate_path": "/api/admin/orders/config/validate",
104+
},
101105
})
102106
if err != nil {
103107
t.Fatalf("RegisterContribution: %v", err)
@@ -117,6 +121,13 @@ func TestDashboardModuleServiceInvoker(t *testing.T) {
117121
if len(contributions) != 1 || contributions[0]["id"] != "orders" {
118122
t.Fatalf("unexpected contributions: %#v", contributions)
119123
}
124+
metadata, ok := contributions[0]["metadata"].(map[string]any)
125+
if !ok {
126+
t.Fatalf("metadata type = %T, want map[string]any", contributions[0]["metadata"])
127+
}
128+
if metadata["validate_path"] != "/api/admin/orders/config/validate" {
129+
t.Fatalf("metadata validate_path = %v", metadata["validate_path"])
130+
}
120131
}
121132

122133
func TestDashboardModuleAuthorizeDefaultsDeny(t *testing.T) {

0 commit comments

Comments
 (0)