Skip to content

Commit 613df56

Browse files
committed
feat: add SHOW ACCESS ON NANOFLOW and DESCRIBE MERMAID for nanoflows
1 parent 802ca63 commit 613df56

9 files changed

Lines changed: 2276 additions & 2140 deletions

File tree

mdl/ast/ast_query.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const (
5555
ShowAccessOnMicroflow // SHOW ACCESS ON MICROFLOW Module.MF
5656
ShowAccessOnPage // SHOW ACCESS ON PAGE Module.Page
5757
ShowAccessOnWorkflow // SHOW ACCESS ON WORKFLOW Module.WF
58+
ShowAccessOnNanoflow // SHOW ACCESS ON NANOFLOW Module.NF
5859
ShowSecurityMatrix // SHOW SECURITY MATRIX [IN module]
5960

6061
// OData show types
@@ -160,6 +161,8 @@ func (t ShowObjectType) String() string {
160161
return "ACCESS ON PAGE"
161162
case ShowAccessOnWorkflow:
162163
return "ACCESS ON WORKFLOW"
164+
case ShowAccessOnNanoflow:
165+
return "ACCESS ON NANOFLOW"
163166
case ShowSecurityMatrix:
164167
return "SECURITY MATRIX"
165168
case ShowODataClients:

mdl/executor/cmd_mermaid.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ func describeMermaid(ctx *ExecContext, objectType, name string) error {
3737
return microflowToMermaid(ctx, qn)
3838
case "page":
3939
return pageToMermaid(ctx, qn)
40+
case "nanoflow":
41+
return nanoflowToMermaid(ctx, qn)
4042
default:
4143
return mdlerrors.NewUnsupported(fmt.Sprintf("mermaid format not supported for type: %s", objectType))
4244
}
@@ -223,22 +225,56 @@ func microflowToMermaid(ctx *ExecContext, name ast.QualifiedName) error {
223225
return mdlerrors.NewNotFound("microflow", name.String())
224226
}
225227

226-
return renderMicroflowMermaid(ctx, targetMf, entityNames)
228+
return renderFlowMermaid(ctx, targetMf.ObjectCollection, entityNames)
227229
}
228230

229-
// renderMicroflowMermaid renders a microflow as a Mermaid flowchart.
230-
func renderMicroflowMermaid(ctx *ExecContext, mf *microflows.Microflow, entityNames map[model.ID]string) error {
231+
// nanoflowToMermaid renders a nanoflow as a Mermaid flowchart.
232+
func nanoflowToMermaid(ctx *ExecContext, name ast.QualifiedName) error {
233+
h, err := getHierarchy(ctx)
234+
if err != nil {
235+
return mdlerrors.NewBackend("build hierarchy", err)
236+
}
237+
238+
// Build entity name lookup
239+
entityNames := make(map[model.ID]string)
240+
domainModels, _ := ctx.Backend.ListDomainModels()
241+
for _, dm := range domainModels {
242+
modName := h.GetModuleName(dm.ContainerID)
243+
for _, entity := range dm.Entities {
244+
entityNames[entity.ID] = modName + "." + entity.Name
245+
}
246+
}
247+
248+
// Find the nanoflow
249+
allNanoflows, err := ctx.Backend.ListNanoflows()
250+
if err != nil {
251+
return mdlerrors.NewBackend("list nanoflows", err)
252+
}
253+
254+
for _, nf := range allNanoflows {
255+
modID := h.FindModuleID(nf.ContainerID)
256+
modName := h.GetModuleName(modID)
257+
if modName == name.Module && nf.Name == name.Name {
258+
return renderFlowMermaid(ctx, nf.ObjectCollection, entityNames)
259+
}
260+
}
261+
262+
return mdlerrors.NewNotFound("nanoflow", name.String())
263+
}
264+
265+
// renderFlowMermaid renders a flow's object collection as a Mermaid flowchart.
266+
func renderFlowMermaid(ctx *ExecContext, oc *microflows.MicroflowObjectCollection, entityNames map[model.ID]string) error {
231267
var sb strings.Builder
232268
sb.WriteString("flowchart LR\n")
233269

234-
if mf.ObjectCollection == nil || len(mf.ObjectCollection.Objects) == 0 {
270+
if oc == nil || len(oc.Objects) == 0 {
235271
sb.WriteString(" start([Start]) --> stop([End])\n")
236272
fmt.Fprint(ctx.Output, sb.String())
237273
return nil
238274
}
239275

240276
// Collect all objects and flows recursively (including nested loop bodies)
241-
allObjects, allFlows := collectAllObjectsAndFlows(mf.ObjectCollection)
277+
allObjects, allFlows := collectAllObjectsAndFlows(oc)
242278

243279
// Build activity map and find start event
244280
activityMap := make(map[model.ID]microflows.MicroflowObject)

mdl/executor/cmd_mermaid_mock_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func TestDescribeMermaid_UnsupportedType(t *testing.T) {
114114
}
115115

116116
ctx, _ := newMockCtx(t, withBackend(mb))
117-
err := describeMermaid(ctx, "nanoflow", "MyModule.Something")
117+
err := describeMermaid(ctx, "workflow", "MyModule.Something")
118118
assertError(t, err)
119119
assertContainsStr(t, fmt.Sprint(err), "not supported")
120120
}

mdl/executor/cmd_security.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,52 @@ func listAccessOnWorkflow(ctx *ExecContext, name *ast.QualifiedName) error {
401401
return mdlerrors.NewUnsupported("show access on workflow is not supported: Mendix workflows do not have document-level AllowedModuleRoles (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting")
402402
}
403403

404+
// listAccessOnNanoflow handles SHOW ACCESS ON NANOFLOW Module.NF.
405+
func listAccessOnNanoflow(ctx *ExecContext, name *ast.QualifiedName) error {
406+
if name == nil {
407+
return mdlerrors.NewValidation("nanoflow name required")
408+
}
409+
410+
h, err := getHierarchy(ctx)
411+
if err != nil {
412+
return mdlerrors.NewBackend("build hierarchy", err)
413+
}
414+
415+
nfs, err := ctx.Backend.ListNanoflows()
416+
if err != nil {
417+
return mdlerrors.NewBackend("list nanoflows", err)
418+
}
419+
420+
for _, nf := range nfs {
421+
modName := h.GetModuleName(h.FindModuleID(nf.ContainerID))
422+
if modName == name.Module && nf.Name == name.Name {
423+
if ctx.Format == FormatJSON {
424+
result := &TableResult{Columns: []string{"Module", "Role"}}
425+
for _, role := range nf.AllowedModuleRoles {
426+
parts := strings.SplitN(string(role), ".", 2)
427+
mod, r := "", string(role)
428+
if len(parts) == 2 {
429+
mod, r = parts[0], parts[1]
430+
}
431+
result.Rows = append(result.Rows, []any{mod, r})
432+
}
433+
return writeResult(ctx, result)
434+
}
435+
if len(nf.AllowedModuleRoles) == 0 {
436+
fmt.Fprintf(ctx.Output, "No module roles granted execute access on %s.%s\n", modName, nf.Name)
437+
return nil
438+
}
439+
fmt.Fprintf(ctx.Output, "Allowed module roles for %s.%s:\n", modName, nf.Name)
440+
for _, role := range nf.AllowedModuleRoles {
441+
fmt.Fprintf(ctx.Output, " %s\n", string(role))
442+
}
443+
return nil
444+
}
445+
}
446+
447+
return mdlerrors.NewNotFound("nanoflow", name.String())
448+
}
449+
404450
// listSecurityMatrix handles SHOW SECURITY MATRIX [IN module].
405451
func listSecurityMatrix(ctx *ExecContext, moduleName string) error {
406452
if ctx.Format == FormatJSON {

mdl/executor/executor_query.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ func execShow(ctx *ExecContext, s *ast.ShowStmt) error {
7575
return listAccessOnPage(ctx, s.Name)
7676
case ast.ShowAccessOnWorkflow:
7777
return listAccessOnWorkflow(ctx, s.Name)
78+
case ast.ShowAccessOnNanoflow:
79+
return listAccessOnNanoflow(ctx, s.Name)
7880
case ast.ShowSecurityMatrix:
7981
return listSecurityMatrix(ctx, s.InModule)
8082
case ast.ShowODataClients:

mdl/grammar/MDLParser.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3075,6 +3075,7 @@ showStatement
30753075
| showOrList ACCESS ON MICROFLOW qualifiedName
30763076
| showOrList ACCESS ON PAGE qualifiedName
30773077
| showOrList ACCESS ON WORKFLOW qualifiedName
3078+
| showOrList ACCESS ON NANOFLOW qualifiedName
30783079
| showOrList SECURITY MATRIX (IN (qualifiedName | IDENTIFIER))?
30793080
| showOrList ODATA CLIENTS (IN (qualifiedName | IDENTIFIER))?
30803081
| showOrList ODATA SERVICES (IN (qualifiedName | IDENTIFIER))?

mdl/grammar/parser/MDLParser.interp

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)