Skip to content

Commit af266b2

Browse files
author
Test
committed
refactor(executor): migrate helpers to free functions, add perf report, fix capstone validation
- cmd_exec.go: add performance report output after script execution - flowbuilder_*, helpers, hierarchy: migrate Executor methods to free functions taking *ExecContext - cmd_microflows_create_v2: adjust microflow creation flow - cmd_security_write_page_v2: update page grant paths - hierarchy.go: add 26 lines of hierarchy caching logic - scripts/validate-academy-capstone.sh: update capstone validation
1 parent eff7d46 commit af266b2

26 files changed

Lines changed: 450 additions & 78 deletions

academy/zh/capstone-helpdesk/参考实现/08-workflow.mdl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,11 @@ create or modify microflow HD.ACT_StartEscalation_WF_FromObject
299299

300300
-- ============================================================
301301
-- 在工单详情页添加"发起升级"按钮
302+
-- 幂等安全:先删除已有按钮再插入,避免重复执行时的冲突
302303
-- ============================================================
303304

304305
alter page HD.Ticket_Detail {
306+
drop widget btnEscalate;
305307
insert after btnComment {
306308
actionbutton btnEscalate (
307309
caption: 'Escalate',

cmd/mxcli/cmd_exec.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"os"
9+
"time"
910

1011
"github.com/mendixlabs/mxcli/mdl/executor"
1112
"github.com/mendixlabs/mxcli/mdl/visitor"
@@ -68,13 +69,18 @@ Example:
6869
return fmt.Errorf("parse failed with %d error(s)", len(errs))
6970
}
7071

72+
progStart := time.Now()
7173
if err := exec.ExecuteProgram(prog); err != nil {
7274
if errors.Is(err, executor.ErrExit) {
7375
return nil
7476
}
7577
fmt.Fprintf(errOut, "Error: %v\n", err)
7678
return err
7779
}
80+
// Print performance report to stderr.
81+
exec.PerfReport(errOut)
82+
elapsed := time.Since(progStart)
83+
fmt.Fprintf(errOut, " Script time: %s\n", executor.FormatDuration(elapsed))
7884
return nil
7985
},
8086
}

mdl/executor/businessevents.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ func createBusinessEventService(ctx *ExecContext, stmt *ast.CreateBusinessEventS
294294
// Resolve folder if specified
295295
containerID := module.ID
296296
if stmt.Folder != "" {
297-
folderID, err := resolveFolder(ctx, module.ID, stmt.Folder)
297+
folderID, err := resolveFolder(ctx, module.ID, stmt.Folder, nil)
298298
if err != nil {
299299
return mdlerrors.NewBackend(fmt.Sprintf("resolve folder '%s'", stmt.Folder), err)
300300
}

mdl/executor/cmd_catalog.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,11 @@ func loadCachedCatalog(ctx *ExecContext, cachePath string) error {
325325

326326
// formatDuration formats a duration in a human-readable way.
327327
func formatDuration(d time.Duration) string {
328+
if d < time.Second {
329+
return fmt.Sprintf("%dms", d.Milliseconds())
330+
}
328331
if d < time.Minute {
329-
return fmt.Sprintf("%.0fs", d.Seconds())
332+
return fmt.Sprintf("%.2fs", d.Seconds())
330333
}
331334
if d < time.Hour {
332335
return fmt.Sprintf("%.0fm", d.Minutes())
@@ -337,6 +340,13 @@ func formatDuration(d time.Duration) string {
337340
return fmt.Sprintf("%.1fd", d.Hours()/24)
338341
}
339342

343+
// FormatDuration is the exported equivalent of formatDuration.
344+
// It returns a human-readable duration string with millisecond precision
345+
// for sub-second values and second precision for longer values.
346+
func FormatDuration(d time.Duration) string {
347+
return formatDuration(d)
348+
}
349+
340350
// buildCatalog builds the catalog from the project.
341351
func buildCatalog(ctx *ExecContext, full bool, source ...bool) error {
342352
isSource := len(source) > 0 && source[0]

mdl/executor/cmd_folders.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func execMoveFolder(ctx *ExecContext, s *ast.MoveFolderStmt) error {
116116
// Resolve target container
117117
var targetContainerID model.ID
118118
if s.TargetFolder != "" {
119-
targetContainerID, err = resolveFolder(ctx, targetModule.ID, s.TargetFolder)
119+
targetContainerID, err = resolveFolder(ctx, targetModule.ID, s.TargetFolder, nil)
120120
if err != nil {
121121
return mdlerrors.NewBackend("resolve target folder", err)
122122
}

mdl/executor/cmd_jsonstructures.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func execCreateJsonStructure(ctx *ExecContext, s *ast.CreateJsonStructureStmt) e
186186
// Resolve folder if specified
187187
containerID := module.ID
188188
if s.Folder != "" {
189-
folderID, err := resolveFolder(ctx, module.ID, s.Folder)
189+
folderID, err := resolveFolder(ctx, module.ID, s.Folder, nil)
190190
if err != nil {
191191
return mdlerrors.NewBackend("resolve folder "+s.Folder, err)
192192
}

mdl/executor/cmd_microflows_create_v2.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ func execCreateMicroflowGen(ctx *ExecContext, s *ast.CreateMicroflowStmt) error
7272

7373
containerID := module.ID
7474
if s.Folder != "" {
75-
folderID, err := resolveFolder(ctx, module.ID, s.Folder)
75+
h, _ := getHierarchy(ctx)
76+
folderID, err := resolveFolder(ctx, module.ID, s.Folder, h)
7677
if err != nil {
7778
return mdlerrors.NewBackend("resolve folder "+s.Folder, err)
7879
}
@@ -82,32 +83,34 @@ func execCreateMicroflowGen(ctx *ExecContext, s *ast.CreateMicroflowStmt) error
8283
qualifiedName := s.Name.Module + "." + s.Name.Name
8384

8485
// ── Existence + replace handling ──────────────────────────
86+
// Uses listMicroflowsWithContainerGen (which caches the ListAll +
87+
// GetContainerUUID pair) instead of raw ListAll + per-element
88+
// genFlowContainerModule, so that consecutive statements in the same
89+
// script reuse one batch fetch when the cache isn't invalidated.
8590
var (
8691
existing *genMf.Microflow
8792
existingContainerID model.ID
8893
existingAllowedRoles []string
8994
preserveAllowedRoles bool
9095
)
91-
all, err := ctx.Microflows.ListAll()
96+
items, err := listMicroflowsWithContainerGen(ctx)
9297
if err != nil {
9398
return mdlerrors.NewBackend("check existing microflows", err)
9499
}
95100
h, _ := getHierarchy(ctx)
96-
for _, mf := range all {
97-
if mf == nil {
101+
for _, item := range items {
102+
if item.MF == nil {
98103
continue
99104
}
100-
modName := genFlowContainerModule(ctx, h, model.ID(mf.ID()))
101-
if modName == s.Name.Module && mf.Name() == s.Name.Name {
105+
modName := containerModuleName(h, item.ContainerUUID)
106+
if modName == s.Name.Module && item.MF.Name() == s.Name.Name {
102107
if !s.CreateOrModify {
103108
return mdlerrors.NewAlreadyExistsMsg("microflow", qualifiedName,
104109
"microflow '"+qualifiedName+"' already exists (use create or modify to overwrite)")
105110
}
106-
existing = mf
107-
if cid, err := ctx.Microflows.GetContainerUUID(model.ID(mf.ID())); err == nil && cid != "" {
108-
existingContainerID = cid
109-
}
110-
existingAllowedRoles = append([]string{}, mf.AllowedModuleRolesQualifiedNames()...)
111+
existing = item.MF
112+
existingContainerID = item.ContainerUUID
113+
existingAllowedRoles = append([]string{}, item.MF.AllowedModuleRolesQualifiedNames()...)
111114
preserveAllowedRoles = true
112115
break
113116
}
@@ -260,7 +263,7 @@ func execCreateMicroflowGen(ctx *ExecContext, s *ast.CreateMicroflowStmt) error
260263
// Resolve TypeEnumeration vs TypeEntity ambiguity: a bare qualified
261264
// name (e.g. HD.Ticket) is parsed as TypeEnumeration — check the
262265
// backend to determine the true kind.
263-
paramType := resolveAmbiguousDataType(ctx.Backend, p.Type)
266+
paramType := resolveAmbiguousDataType(ctx, ctx.Backend, p.Type)
264267
if dt := convertASTToGenDataType(paramType); dt != nil {
265268
param.SetParameterType(dt)
266269
}
@@ -321,6 +324,12 @@ func execCreateMicroflowGen(ctx *ExecContext, s *ast.CreateMicroflowStmt) error
321324
ctx.trackCreatedMicroflow(s.Name.Module, s.Name.Name, model.ID(mf.ID()), containerID, returnEntityName)
322325

323326
invalidateHierarchy(ctx)
324-
invalidateMicroflowsCache(ctx)
327+
// Only invalidate microflow caches on CREATE (new name enters the
328+
// project). On MODIFY the name → ID mapping and container UUID are
329+
// unchanged, so the cached list & container UUIDs remain valid and
330+
// can serve subsequent statements without re-fetching.
331+
if existing == nil {
332+
invalidateMicroflowsCache(ctx)
333+
}
325334
return nil
326335
}

mdl/executor/cmd_move.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func execMove(ctx *ExecContext, s *ast.MoveStmt) error {
5050
// Resolve target container (folder or module root)
5151
var targetContainerID model.ID
5252
if s.Folder != "" {
53-
targetContainerID, err = resolveFolder(ctx, targetModule.ID, s.Folder)
53+
targetContainerID, err = resolveFolder(ctx, targetModule.ID, s.Folder, nil)
5454
if err != nil {
5555
return mdlerrors.NewBackend("resolve target folder", err)
5656
}

mdl/executor/cmd_nanoflows_create_v2.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ func execCreateNanoflowGen(ctx *ExecContext, s *ast.CreateNanoflowStmt) error {
6262

6363
containerID := module.ID
6464
if s.Folder != "" {
65-
folderID, err := resolveFolder(ctx, module.ID, s.Folder)
65+
h, _ := getHierarchy(ctx)
66+
folderID, err := resolveFolder(ctx, module.ID, s.Folder, h)
6667
if err != nil {
6768
return mdlerrors.NewBackend("resolve folder "+s.Folder, err)
6869
}
@@ -168,7 +169,7 @@ func execCreateNanoflowGen(ctx *ExecContext, s *ast.CreateNanoflowStmt) error {
168169
// Resolve TypeEnumeration vs TypeEntity ambiguity: a bare qualified
169170
// name (e.g. HD.Ticket) is parsed as TypeEnumeration — check the
170171
// backend to determine the true kind.
171-
paramType := resolveAmbiguousDataType(ctx.Backend, p.Type)
172+
paramType := resolveAmbiguousDataType(ctx, ctx.Backend, p.Type)
172173
if dt := convertASTToGenDataType(paramType); dt != nil {
173174
param.SetParameterType(dt)
174175
}

mdl/executor/cmd_odata.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ func createODataClient(ctx *ExecContext, stmt *ast.CreateODataClientStmt) error
10421042
// Resolve folder if specified
10431043
containerID := module.ID
10441044
if stmt.Folder != "" {
1045-
folderID, err := resolveFolder(ctx, module.ID, stmt.Folder)
1045+
folderID, err := resolveFolder(ctx, module.ID, stmt.Folder, nil)
10461046
if err != nil {
10471047
return mdlerrors.NewBackend(fmt.Sprintf("resolve folder %s", stmt.Folder), err)
10481048
}
@@ -1369,7 +1369,7 @@ func createODataService(ctx *ExecContext, stmt *ast.CreateODataServiceStmt) erro
13691369
// Resolve folder if specified
13701370
containerID := module.ID
13711371
if stmt.Folder != "" {
1372-
folderID, err := resolveFolder(ctx, module.ID, stmt.Folder)
1372+
folderID, err := resolveFolder(ctx, module.ID, stmt.Folder, nil)
13731373
if err != nil {
13741374
return mdlerrors.NewBackend(fmt.Sprintf("resolve folder %s", stmt.Folder), err)
13751375
}

0 commit comments

Comments
 (0)