Skip to content

Commit b04c62e

Browse files
authored
Merge pull request #251 from mendixlabs/fix/171-workflow-user-targeting-rebased
feat: support full user targeting on workflow USER TASK (fixes #169)
2 parents 3e711b9 + b1d7ea0 commit b04c62e

20 files changed

Lines changed: 8448 additions & 7924 deletions

cmd/mxcli/lsp_completions_gen.go

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

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ Nested folders use `/` separator: `'Parent/Child/Grandchild'`. Missing folders a
304304
| Drop workflow | `drop workflow Module.Name;` | |
305305

306306
**Workflow Activity Types:**
307-
- `user task <name> '<caption>' [page Mod.Page] [targeting microflow Mod.MF] [outcomes '<out>' { } ...];`
307+
- `user task <name> '<caption>' [page Mod.Page] [targeting [users|groups] microflow Mod.MF] [targeting [users|groups] xpath '<expr>'] [outcomes '<out>' { } ...];`
308308
- `call microflow Mod.MF [comment '<text>'] [outcomes '<out>' { } ...];`
309309
- `call workflow Mod.WF [comment '<text>'];`
310310
- `decision ['<caption>'] outcomes '<out>' { } ...;`
@@ -340,8 +340,8 @@ Modify an existing workflow's properties, activities, outcomes, paths, condition
340340
| Set parameter | `set parameter $Var: Module.Entity` | Workflow context parameter |
341341
| Set activity page | `set activity name page Module.Page` | Change user task page |
342342
| Set activity description | `set activity name description 'text'` | Activity description |
343-
| Set activity targeting | `set activity name targeting microflow Module.MF` | Target user assignment |
344-
| Set activity XPath | `set activity name targeting xpath '[expr]'` | XPath targeting |
343+
| Set activity targeting | `set activity name targeting [users\|groups] microflow Module.MF` | Target user/group assignment |
344+
| Set activity XPath | `set activity name targeting [users\|groups] xpath '[expr]'` | XPath targeting |
345345
| Set activity due date | `set activity name due date 'expr'` | Activity-level due date |
346346
| Insert activity | `insert after name call microflow Module.MF` | Insert after named activity |
347347
| Drop activity | `drop activity name` | Remove activity by name |

docs/11-proposals/PROPOSAL_agent_document_support.md

Lines changed: 779 additions & 732 deletions
Large diffs are not rendered by default.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
-- ============================================================================
2+
-- Workflow User Targeting — all targeting variants
3+
-- ============================================================================
4+
--
5+
-- Covers USER TASK and MULTI USER TASK with:
6+
-- TARGETING USERS MICROFLOW (user targeting via microflow)
7+
-- TARGETING USERS XPATH (user targeting via XPath)
8+
-- TARGETING GROUPS MICROFLOW (group targeting via microflow)
9+
-- TARGETING GROUPS XPATH (group targeting via XPath)
10+
-- TARGETING MICROFLOW (legacy short form, still valid)
11+
-- TARGETING XPATH (legacy short form, still valid)
12+
--
13+
-- ============================================================================
14+
15+
-- Prerequisites
16+
17+
CREATE PERSISTENT ENTITY WFTarget.Request (
18+
Status: String(200)
19+
);
20+
21+
-- Targeting microflows: (System.Workflow, context) -> List of System.User
22+
CREATE MICROFLOW WFTarget.ACT_GetUsers (
23+
$Workflow: System.Workflow,
24+
$Context: WFTarget.Request
25+
)
26+
RETURNS List of System.User AS $Users
27+
BEGIN
28+
@position(200,200)
29+
RETRIEVE $Users FROM System.User;
30+
@position(400,200) RETURN $Users;
31+
END;
32+
/
33+
34+
CREATE MICROFLOW WFTarget.ACT_GetGroups (
35+
$Workflow: System.Workflow,
36+
$Context: WFTarget.Request
37+
)
38+
RETURNS List of System.UserGroup AS $Groups
39+
BEGIN
40+
@position(200,200)
41+
RETRIEVE $Groups FROM System.UserGroup;
42+
@position(400,200) RETURN $Groups;
43+
END;
44+
/
45+
46+
CREATE PAGE WFTarget.TaskPage (
47+
Title: 'Task Page',
48+
Layout: Atlas_Core.Atlas_Default,
49+
Params: { $WorkflowUserTask: System.WorkflowUserTask }
50+
) {
51+
LAYOUTGRID g1 {
52+
ROW r1 {
53+
COLUMN c1 (DesktopWidth: 12) {
54+
DYNAMICTEXT txt1 (Content: 'Task', RenderMode: H2)
55+
}
56+
}
57+
}
58+
}
59+
60+
-- Workflow with all targeting variants
61+
62+
CREATE WORKFLOW WFTarget.TargetingDemo
63+
PARAMETER $WorkflowContext: WFTarget.Request
64+
BEGIN
65+
-- TARGETING USERS MICROFLOW (explicit user targeting)
66+
USER TASK ByUserMF 'User microflow targeting'
67+
PAGE WFTarget.TaskPage
68+
TARGETING USERS MICROFLOW WFTarget.ACT_GetUsers
69+
OUTCOMES 'Done' { };
70+
71+
-- TARGETING USERS XPATH (explicit user xpath targeting)
72+
USER TASK ByUserXP 'User xpath targeting'
73+
PAGE WFTarget.TaskPage
74+
TARGETING USERS XPATH '[Name = ''admin'']'
75+
OUTCOMES 'Done' { };
76+
77+
-- TARGETING GROUPS MICROFLOW (group microflow targeting)
78+
USER TASK ByGroupMF 'Group microflow targeting'
79+
PAGE WFTarget.TaskPage
80+
TARGETING GROUPS MICROFLOW WFTarget.ACT_GetGroups
81+
OUTCOMES 'Done' { };
82+
83+
-- TARGETING GROUPS XPATH (group xpath targeting)
84+
USER TASK ByGroupXP 'Group xpath targeting'
85+
PAGE WFTarget.TaskPage
86+
TARGETING GROUPS XPATH '[Name = ''Administrators'']'
87+
OUTCOMES 'Done' { };
88+
89+
-- TARGETING MICROFLOW (legacy short form)
90+
USER TASK LegacyMF 'Legacy microflow targeting'
91+
PAGE WFTarget.TaskPage
92+
TARGETING MICROFLOW WFTarget.ACT_GetUsers
93+
OUTCOMES 'Done' { };
94+
95+
-- TARGETING XPATH (legacy short form)
96+
USER TASK LegacyXP 'Legacy xpath targeting'
97+
PAGE WFTarget.TaskPage
98+
TARGETING XPATH '[Name = ''admin'']'
99+
OUTCOMES 'Done' { };
100+
101+
-- MULTI USER TASK with group targeting
102+
MULTI USER TASK MultiByGroup 'Multi group targeting'
103+
PAGE WFTarget.TaskPage
104+
TARGETING GROUPS MICROFLOW WFTarget.ACT_GetGroups
105+
OUTCOMES 'Done' { };
106+
107+
END WORKFLOW;
108+
/

mdl/ast/ast_workflow.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@ type WorkflowUserTaskNode struct {
5656
func (n *WorkflowUserTaskNode) workflowActivityNode() {}
5757

5858
// WorkflowTargetingNode represents user targeting strategy.
59+
// Kind: "microflow", "xpath", "group_microflow", "group_xpath", or ""
5960
type WorkflowTargetingNode struct {
60-
Kind string // "microflow", "xpath", or ""
61-
Microflow QualifiedName // for microflow targeting
62-
XPath string // for xpath targeting
61+
Kind string // "microflow", "xpath", "group_microflow", "group_xpath", or ""
62+
Microflow QualifiedName // for microflow targeting (user or group)
63+
XPath string // for xpath targeting (user or group)
6364
}
6465

6566
// WorkflowUserTaskOutcomeNode represents an outcome of a user task.

mdl/executor/cmd_workflows.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,11 +426,19 @@ func formatUserTask(a *workflows.UserTask, indent string) []string {
426426
switch us := a.UserSource.(type) {
427427
case *workflows.MicroflowBasedUserSource:
428428
if us.Microflow != "" {
429-
lines = append(lines, fmt.Sprintf("%s targeting microflow %s", indent, us.Microflow))
429+
lines = append(lines, fmt.Sprintf("%s targeting users microflow %s", indent, us.Microflow))
430430
}
431431
case *workflows.XPathBasedUserSource:
432432
if us.XPath != "" {
433-
lines = append(lines, fmt.Sprintf("%s targeting xpath '%s'", indent, us.XPath))
433+
lines = append(lines, fmt.Sprintf("%s targeting users xpath '%s'", indent, us.XPath))
434+
}
435+
case *workflows.MicroflowGroupSource:
436+
if us.Microflow != "" {
437+
lines = append(lines, fmt.Sprintf("%s targeting groups microflow %s", indent, us.Microflow))
438+
}
439+
case *workflows.XPathGroupSource:
440+
if us.XPath != "" {
441+
lines = append(lines, fmt.Sprintf("%s targeting groups xpath '%s'", indent, us.XPath))
434442
}
435443
}
436444
}

mdl/executor/cmd_workflows_write.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,14 @@ func buildUserTask(n *ast.WorkflowUserTaskNode) *workflows.UserTask {
251251
task.UserSource = &workflows.XPathBasedUserSource{
252252
XPath: n.Targeting.XPath,
253253
}
254+
case "group_microflow":
255+
task.UserSource = &workflows.MicroflowGroupSource{
256+
Microflow: n.Targeting.Microflow.Module + "." + n.Targeting.Microflow.Name,
257+
}
258+
case "group_xpath":
259+
task.UserSource = &workflows.XPathGroupSource{
260+
XPath: n.Targeting.XPath,
261+
}
254262
}
255263

256264
// Outcomes
@@ -667,11 +675,25 @@ func autoBindActivitiesInFlow(ctx *ExecContext, activities []workflows.WorkflowA
667675
}
668676

669677
// autoBindCallMicroflow resolves microflow parameters and auto-generates ParameterMappings.
678+
// Also ensures a default VoidConditionOutcome exists (required by Mendix runtime — CE6686).
670679
func autoBindCallMicroflow(ctx *ExecContext, task *workflows.CallMicroflowTask) {
671680
// Sanitize name
672681
task.Name = sanitizeActivityName(task.Name)
673682

674-
// Skip if already has parameter mappings
683+
// Auto-generate Default outcome if no outcomes specified.
684+
// This must run regardless of whether parameter mappings exist,
685+
// because the Mendix runtime requires a VoidConditionOutcome on
686+
// every CallMicroflowTask (CE6686: "outcomes do not match").
687+
if len(task.Outcomes) == 0 {
688+
outcome := &workflows.VoidConditionOutcome{
689+
Flow: &workflows.Flow{},
690+
}
691+
outcome.BaseElement.ID = model.ID(types.GenerateID())
692+
outcome.Flow.BaseElement.ID = model.ID(types.GenerateID())
693+
task.Outcomes = append(task.Outcomes, outcome)
694+
}
695+
696+
// Skip parameter auto-binding if already has explicit mappings
675697
if len(task.ParameterMappings) > 0 {
676698
return
677699
}
@@ -705,16 +727,6 @@ func autoBindCallMicroflow(ctx *ExecContext, task *workflows.CallMicroflowTask)
705727
mapping.BaseElement.ID = model.ID(types.GenerateID())
706728
task.ParameterMappings = append(task.ParameterMappings, mapping)
707729
}
708-
709-
// Auto-generate Default outcome if no outcomes specified
710-
if len(task.Outcomes) == 0 {
711-
outcome := &workflows.VoidConditionOutcome{
712-
Flow: &workflows.Flow{},
713-
}
714-
outcome.BaseElement.ID = model.ID(types.GenerateID())
715-
outcome.Flow.BaseElement.ID = model.ID(types.GenerateID())
716-
task.Outcomes = append(task.Outcomes, outcome)
717-
}
718730
break
719731
}
720732
}

mdl/grammar/MDLLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ ACTIVITY: A C T I V I T Y;
659659
CONDITION: C O N D I T I O N;
660660
OFF: O F F;
661661
USERS: U S E R S;
662+
GROUPS: G R O U P S;
662663

663664
// Data transformer tokens
664665
DATA: D A T A;

mdl/grammar/MDLParser.g4

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,17 +2820,17 @@ workflowActivityStmt
28202820
workflowUserTaskStmt
28212821
: USER TASK IDENTIFIER STRING_LITERAL
28222822
(PAGE qualifiedName)?
2823-
(TARGETING MICROFLOW qualifiedName)?
2824-
(TARGETING XPATH STRING_LITERAL)?
2823+
(TARGETING (USERS | GROUPS)? MICROFLOW qualifiedName)?
2824+
(TARGETING (USERS | GROUPS)? XPATH STRING_LITERAL)?
28252825
(ENTITY qualifiedName)?
28262826
(DUE DATE_TYPE STRING_LITERAL)?
28272827
(DESCRIPTION STRING_LITERAL)?
28282828
(OUTCOMES workflowUserTaskOutcome+)?
28292829
(BOUNDARY EVENT workflowBoundaryEventClause+)?
28302830
| MULTI USER TASK IDENTIFIER STRING_LITERAL
28312831
(PAGE qualifiedName)?
2832-
(TARGETING MICROFLOW qualifiedName)?
2833-
(TARGETING XPATH STRING_LITERAL)?
2832+
(TARGETING (USERS | GROUPS)? MICROFLOW qualifiedName)?
2833+
(TARGETING (USERS | GROUPS)? XPATH STRING_LITERAL)?
28342834
(ENTITY qualifiedName)?
28352835
(DUE DATE_TYPE STRING_LITERAL)?
28362836
(DESCRIPTION STRING_LITERAL)?
@@ -3860,7 +3860,7 @@ keyword
38603860

38613861
// Workflow
38623862
| ABORT | ACTIVITY | ANNOTATION | BOUNDARY | BY | COMPLETE_TASK
3863-
| CONDITION | DATE | DECISION | DUE | INTERRUPTING | JUMP
3863+
| CONDITION | DATE | DECISION | DUE | GROUPS | INTERRUPTING | JUMP
38643864
| LOCK | MULTI | NODE | NON | NOTIFICATION | NOTIFY
38653865
| OPEN | OUTCOME | OUTCOMES | OVERVIEW | PARALLEL | PAUSE
38663866
| REASON | RESTART | RETRY | SPLIT | TARGETING | TASK | TIMER

mdl/grammar/parser/MDLLexer.interp

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

0 commit comments

Comments
 (0)