Skip to content

Commit 2661d49

Browse files
committed
Merge remote-tracking branch 'origin/main' into copilot/implement-search-functionality
2 parents 339bb7e + 31b1ed4 commit 2661d49

File tree

10 files changed

+475
-10
lines changed

10 files changed

+475
-10
lines changed

.github/copilot-instructions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ Follow conventions in COMMIT-MESSAGE-GUIDELINE.md.
1414
- README.md, EXAMPLES.md and other documentation
1515
- shell completion
1616

17+
- for every implementation, make sure to add or update tests that cover the new functionality. This includes unit tests for individual functions and integration tests for end-to-end scenarios. Tests should be comprehensive and cover edge cases to ensure the robustness of the codebase.
18+
19+
- in any test, only use the implemented cli commands to interact with the system. Avoid using internal functions or direct API calls in tests, as this can lead to brittle tests that are tightly coupled to the implementation. By using the cli commands, you ensure that your tests are more resilient to changes in the underlying code and better reflect real-world usage.
20+
21+
- don't use Promises in tests to wait for the overall system status to settle. Instead, use the polling helper from tests/utils/polling.ts to wait for specific conditions to be met.
22+
1723
- as a final Quality Gate before running tests, make sure to run `npm run build` to catch any compilation errors that might be missed by the test suite. This is especially important for catching type errors and ensuring that the codebase remains robust and maintainable.
1824

1925
### Work Environment

EXAMPLES.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Comprehensive examples for all c8ctl operations.
1010
- [Jobs](#jobs)
1111
- [Messages](#messages)
1212
- [Deployments](#deployments)
13+
- [Forms](#forms)
1314
- [Topology](#topology)
1415
- [Profile Management](#profile-management)
1516
- [Session Management](#session-management)
@@ -357,6 +358,51 @@ c8 run ./order-process.bpmn --variables='{"orderId":"12345","amount":100}'
357358

358359
---
359360

361+
## Forms
362+
363+
Forms in Camunda 8 are linked to user tasks and process definitions. You can retrieve the form associated with a specific resource.
364+
365+
### Get Form (Search Both Types)
366+
367+
```bash
368+
# Search both user task and process definition (no flag required)
369+
c8 get form 2251799813685251
370+
371+
# The output will indicate whether the form was found as a user task, process definition, both, or neither
372+
# Using profile
373+
c8 get form 2251799813685251 --profile prod
374+
```
375+
376+
### Get Form for User Task Only
377+
378+
```bash
379+
# Get the form associated with a user task (short form with --ut alias)
380+
c8 get form 2251799813685251 --ut
381+
382+
# Long form
383+
c8 get form 2251799813685251 --userTask
384+
385+
# Using profile
386+
c8 get form 2251799813685251 --ut --profile prod
387+
```
388+
389+
### Get Start Form for Process Definition Only
390+
391+
```bash
392+
# Get the start form for a process definition (short form with --pd alias)
393+
c8 get form 2251799813685252 --pd
394+
395+
# Long form
396+
c8 get form 2251799813685252 --processDefinition
397+
398+
# Using profile
399+
c8 get form 2251799813685252 --pd --profile prod
400+
```
401+
402+
**Note**: When no flag is specified, the command searches both user tasks and process definitions and reports where the form was found. With a flag, it only searches the specified type.
403+
404+
---
405+
360406
## Topology
361407

362408
### Get Cluster Topology

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ c8ctl await process-instance --id=myProcess
104104
# Cancel process instance
105105
c8ctl cancel pi 123456
106106

107+
# Get forms
108+
c8ctl get form 123456 # Get form (searches both user task and process definition)
109+
c8ctl get form 123456 --ut # Get form for user task only
110+
c8ctl get form 123456 --pd # Get start form for process definition only
111+
107112
# Deploy and run
108113
c8ctl deploy ./my-process.bpmn # Deploy a single file
109114
c8ctl deploy # Deploy current directory

package-lock.json

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

src/commands/completion.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ _c8ctl_completions() {
2525
# Resources by verb
2626
local list_resources="process-instances process-instance pi user-tasks user-task ut incidents incident inc jobs profiles profile plugins plugin"
2727
local search_resources="process-instances process-instance pi process-definitions process-definition pd user-tasks user-task ut incidents incident inc jobs variables variable"
28-
local get_resources="process-instance pi process-definition pd incident inc topology"
28+
local get_resources="process-instance pi process-definition pd incident inc topology form"
2929
local create_resources="process-instance pi"
3030
local cancel_resources="process-instance pi"
3131
local await_resources="process-instance pi"
@@ -46,7 +46,7 @@ _c8ctl_completions() {
4646
local help_resources="list get create complete"
4747
4848
# Global flags
49-
local flags="--help --version --profile --from --all --bpmnProcessId --id --processInstanceKey --processDefinitionKey --parentProcessInstanceKey --variables --state --assignee --type --correlationKey --timeToLive --maxJobsToActivate --timeout --worker --retries --errorMessage --baseUrl --clientId --clientSecret --audience --oAuthUrl --defaultTenantId --awaitCompletion --fetchVariables --name --key --elementId --errorType --value --scopeKey --fullValue"
49+
local flags="--help --version --profile --from --all --bpmnProcessId --id --processInstanceKey --processDefinitionKey --parentProcessInstanceKey --variables --state --assignee --type --correlationKey --timeToLive --maxJobsToActivate --timeout --worker --retries --errorMessage --baseUrl --clientId --clientSecret --audience --oAuthUrl --defaultTenantId --awaitCompletion --fetchVariables --name --key --elementId --errorType --value --scopeKey --fullValue --userTask --ut --processDefinition --pd"
5050
5151
case \${cword} in
5252
1)
@@ -219,6 +219,10 @@ _c8ctl() {
219219
'--value[Variable value]:value:'
220220
'--scopeKey[Scope key]:key:'
221221
'--fullValue[Return full variable values]'
222+
'--userTask[Get form for a user task]'
223+
'--ut[Get form for a user task (alias for --userTask)]'
224+
'--processDefinition[Get start form for a process definition]'
225+
'--pd[Get start form for a process definition (alias for --processDefinition)]'
222226
)
223227
224228
case \$CURRENT in
@@ -275,6 +279,7 @@ _c8ctl() {
275279
'incident:Get incident'
276280
'inc:Get incident'
277281
'topology:Get cluster topology'
282+
'form:Get form for user task or process definition'
278283
)
279284
_describe 'resource' resources
280285
;;
@@ -502,6 +507,14 @@ complete -c c8ctl -l scopeKey -d 'Scope key' -r
502507
complete -c c8 -l scopeKey -d 'Scope key' -r
503508
complete -c c8ctl -l fullValue -d 'Return full variable values'
504509
complete -c c8 -l fullValue -d 'Return full variable values'
510+
complete -c c8ctl -l userTask -d 'Get form for a user task'
511+
complete -c c8 -l userTask -d 'Get form for a user task'
512+
complete -c c8ctl -l ut -d 'Get form for a user task (alias for --userTask)'
513+
complete -c c8 -l ut -d 'Get form for a user task (alias for --userTask)'
514+
complete -c c8ctl -l processDefinition -d 'Get start form for a process definition'
515+
complete -c c8 -l processDefinition -d 'Get start form for a process definition'
516+
complete -c c8ctl -l pd -d 'Get start form for a process definition (alias for --processDefinition)'
517+
complete -c c8 -l pd -d 'Get start form for a process definition (alias for --processDefinition)'
505518
506519
# Commands (verbs) - only suggest when no command is given yet
507520
complete -c c8ctl -n '__fish_use_subcommand' -a 'list' -d 'List resources'
@@ -632,6 +645,8 @@ complete -c c8ctl -n '__fish_seen_subcommand_from get' -a 'inc' -d 'Get incident
632645
complete -c c8 -n '__fish_seen_subcommand_from get' -a 'inc' -d 'Get incident'
633646
complete -c c8ctl -n '__fish_seen_subcommand_from get' -a 'topology' -d 'Get cluster topology'
634647
complete -c c8 -n '__fish_seen_subcommand_from get' -a 'topology' -d 'Get cluster topology'
648+
complete -c c8ctl -n '__fish_seen_subcommand_from get' -a 'form' -d 'Get form for user task or process definition'
649+
complete -c c8 -n '__fish_seen_subcommand_from get' -a 'form' -d 'Get form for user task or process definition'
635650
636651
# Resources for 'create' command
637652
complete -c c8ctl -n '__fish_seen_subcommand_from create' -a 'process-instance' -d 'Create process instance'

src/commands/forms.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* Form commands
3+
*/
4+
5+
import { getLogger } from '../logger.ts';
6+
import { createClient } from '../client.ts';
7+
8+
/**
9+
* Get form for a user task
10+
*/
11+
export async function getUserTaskForm(userTaskKey: string, options: {
12+
profile?: string;
13+
}): Promise<Record<string, unknown> | undefined> {
14+
const logger = getLogger();
15+
const client = createClient(options.profile);
16+
17+
try {
18+
const result = await client.getUserTaskForm(
19+
{ userTaskKey: userTaskKey as any },
20+
{ consistency: { waitUpToMs: 0 } }
21+
);
22+
// API returns null when user task exists but has no form
23+
if (result === null || result === undefined) {
24+
logger.info('User task found but has no associated form');
25+
return undefined;
26+
}
27+
logger.json(result);
28+
return result as Record<string, unknown>;
29+
} catch (error: any) {
30+
// Handle 204 No Content (user task exists but has no form)
31+
if (error.statusCode === 204 || error.status === 204) {
32+
logger.info('User task found but has no associated form');
33+
return undefined;
34+
}
35+
logger.error(`Failed to get form for user task ${userTaskKey}`, error as Error);
36+
process.exit(1);
37+
}
38+
}
39+
40+
/**
41+
* Get start form for a process definition
42+
*/
43+
export async function getStartForm(processDefinitionKey: string, options: {
44+
profile?: string;
45+
}): Promise<Record<string, unknown> | undefined> {
46+
const logger = getLogger();
47+
const client = createClient(options.profile);
48+
49+
try {
50+
const result = await client.getStartProcessForm(
51+
{ processDefinitionKey: processDefinitionKey as any },
52+
{ consistency: { waitUpToMs: 0 } }
53+
);
54+
// API returns null when process definition exists but has no start form
55+
if (result === null || result === undefined) {
56+
logger.info('Process definition found but has no associated start form');
57+
return undefined;
58+
}
59+
logger.json(result);
60+
return result as Record<string, unknown>;
61+
} catch (error: any) {
62+
// Handle 204 No Content (process definition exists but has no form)
63+
if (error.statusCode === 204 || error.status === 204) {
64+
logger.info('Process definition found but has no associated start form');
65+
return undefined;
66+
}
67+
logger.error(`Failed to get start form for process definition ${processDefinitionKey}`, error as Error);
68+
process.exit(1);
69+
}
70+
}
71+
72+
/**
73+
* Get form by trying both user task and process definition APIs
74+
*/
75+
export async function getForm(key: string, options: {
76+
profile?: string;
77+
}): Promise<{ type: string; key: string; form: Record<string, unknown> } | undefined> {
78+
const logger = getLogger();
79+
const client = createClient(options.profile);
80+
81+
const results: { type: string; key: string; form: any }[] = [];
82+
const errors: { type: string; error: any }[] = [];
83+
84+
// Try user task form
85+
try {
86+
const result = await client.getUserTaskForm(
87+
{ userTaskKey: key as any },
88+
{ consistency: { waitUpToMs: 0 } }
89+
);
90+
if (result !== null && result !== undefined) {
91+
results.push({ type: 'user task', key, form: result });
92+
}
93+
} catch (error: any) {
94+
// 204 means resource exists but no form - not an error
95+
if (error.statusCode !== 204 && error.status !== 204) {
96+
errors.push({ type: 'user task', error });
97+
}
98+
}
99+
100+
// Try process definition form
101+
try {
102+
const result = await client.getStartProcessForm(
103+
{ processDefinitionKey: key as any },
104+
{ consistency: { waitUpToMs: 0 } }
105+
);
106+
if (result !== null && result !== undefined) {
107+
results.push({ type: 'process definition', key, form: result });
108+
}
109+
} catch (error: any) {
110+
// 204 means resource exists but no form - not an error
111+
if (error.statusCode !== 204 && error.status !== 204) {
112+
errors.push({ type: 'process definition', error });
113+
}
114+
}
115+
116+
// Report results
117+
if (results.length === 0) {
118+
if (errors.length === 0) {
119+
logger.info('No form found for user task or process definition');
120+
return undefined;
121+
} else if (errors.length === 1) {
122+
logger.error(`Failed to get form: not found as ${errors[0].type}`, errors[0].error as Error);
123+
process.exit(1);
124+
} else {
125+
logger.error(`Failed to get form: not found as user task or process definition`);
126+
process.exit(1);
127+
}
128+
} else if (results.length === 1) {
129+
const keyType = results[0].type === 'user task' ? 'userTaskKey' : 'processDefinitionKey';
130+
logger.info(`Form found for ${results[0].type} (${keyType}: ${key}):`);
131+
logger.json(results[0].form);
132+
return { type: results[0].type, key, form: results[0].form as Record<string, unknown> };
133+
} else {
134+
logger.info(`Form found in both user task and process definition (key: ${key}):`);
135+
const combined = {
136+
userTaskKey: key,
137+
userTask: results.find(r => r.type === 'user task')?.form,
138+
processDefinitionKey: key,
139+
processDefinition: results.find(r => r.type === 'process definition')?.form,
140+
};
141+
logger.json(combined);
142+
return { type: 'both', key, form: combined as Record<string, unknown> };
143+
}
144+
}

src/commands/help.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Usage: c8ctl <command> [resource] [options]
5252
Commands:
5353
list <resource> List resources (pi, pd, ut, inc, jobs, profiles)
5454
search <resource> Search resources with filters (pi, pd, ut, inc, jobs, variables)
55-
get <resource> <key> Get resource by key (pi, pd, inc, topology)
55+
get <resource> <key> Get resource by key (pi, pd, inc, topology, form)
5656
create <resource> Create resource (pi)
5757
cancel <resource> <key> Cancel resource (pi)
5858
await <resource> Create and await completion (pi, alias for create --awaitCompletion)
@@ -81,6 +81,8 @@ Flags:
8181
--from <url> Load plugin from URL (use with 'load plugin')
8282
--xml Get process definition as XML (use with 'get pd')
8383
--variables Get process instance with variables (use with 'get pi')
84+
--userTask, --ut Get form for a user task (optional, use with 'get form')
85+
--processDefinition, --pd Get start form for a process definition (optional, use with 'get form')
8486
--id <process-id> Process definition ID (alias for --bpmnProcessId)
8587
--awaitCompletion Wait for process instance to complete (use with 'create pi')
8688
--fetchVariables <v> Reserved for future use (all variables returned by default)
@@ -125,6 +127,9 @@ Examples:
125127
c8ctl get pi 123456 --variables Get process instance with variables
126128
c8ctl get pd 123456 Get process definition by key
127129
c8ctl get pd 123456 --xml Get process definition XML
130+
c8ctl get form 123456 Get form (searches both user task and process definition)
131+
c8ctl get form 123456 --ut Get form for user task only
132+
c8ctl get form 123456 --pd Get start form for process definition only
128133
c8ctl create pi --id=myProcess
129134
c8ctl create pi --id=myProcess --awaitCompletion
130135
c8ctl await pi --id=myProcess Create and wait for completion
@@ -154,7 +159,7 @@ export function showVerbResources(verb: string): void {
154159
const resources: Record<string, string> = {
155160
list: 'process-instances (pi), process-definitions (pd), user-tasks (ut), incidents (inc), jobs, profiles, plugins',
156161
search: 'process-instances (pi), process-definitions (pd), user-tasks (ut), incidents (inc), jobs, variables',
157-
get: 'process-instance (pi), process-definition (pd), incident (inc), topology',
162+
get: 'process-instance (pi), process-definition (pd), incident (inc), topology, form',
158163
create: 'process-instance (pi)',
159164
complete: 'user-task (ut), job',
160165
cancel: 'process-instance (pi)',
@@ -263,13 +268,23 @@ Resources and their available flags:
263268
topology
264269
--profile <name> Use specific profile
265270
271+
form <key>
272+
--userTask, --ut (Optional) Get form for a user task only
273+
--processDefinition, --pd (Optional) Get start form for a process definition only
274+
--profile <name> Use specific profile
275+
276+
If no flag is specified, searches both user task and process definition.
277+
266278
Examples:
267279
c8ctl get pi 2251799813685249
268280
c8ctl get pi 2251799813685249 --variables
269281
c8ctl get pd 2251799813685250
270282
c8ctl get pd 2251799813685250 --xml
271283
c8ctl get inc 2251799813685251
272284
c8ctl get topology
285+
c8ctl get form 2251799813685251
286+
c8ctl get form 2251799813685251 --ut
287+
c8ctl get form 2251799813685252 --pd
273288
`.trim());
274289
}
275290

0 commit comments

Comments
 (0)