@saltcorn/server 1.1.0 → 1.1.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/locales/en.json +2 -1
- package/locales/pl.json +16 -1
- package/package.json +9 -9
- package/public/saltcorn.css +18 -4
- package/routes/actions.js +161 -170
- package/routes/utils.js +1 -0
- package/tests/view.test.js +37 -0
package/locales/en.json
CHANGED
|
@@ -1521,5 +1521,6 @@
|
|
|
1521
1521
|
"Step settings": "Step settings",
|
|
1522
1522
|
"Action settings": "Action settings",
|
|
1523
1523
|
"Workflow": "Workflow",
|
|
1524
|
-
"Previous runs": "Previous runs"
|
|
1524
|
+
"Previous runs": "Previous runs",
|
|
1525
|
+
"The workflow the user will be interacting with.": "The workflow the user will be interacting with."
|
|
1525
1526
|
}
|
package/locales/pl.json
CHANGED
|
@@ -1508,5 +1508,20 @@
|
|
|
1508
1508
|
"Workflow runs": "Przepływ pracy jest uruchomiony",
|
|
1509
1509
|
"Workflow run": "Uruchomienie przepływu pracy",
|
|
1510
1510
|
"Share to enabled": "Udostępnij włączone",
|
|
1511
|
-
"Enable the share to feature": "Włącz udostępnianie funkcji"
|
|
1511
|
+
"Enable the share to feature": "Włącz udostępnianie funkcji",
|
|
1512
|
+
"Allocate new row": "Przydziel nowy wiersz",
|
|
1513
|
+
"If the view is run without existing row, allocate a new row on load. Defaults must be set on all required fields.": "Jeśli widok jest uruchamiany bez istniejącego wiersza, przydziel nowy wiersz podczas ładowania. Domyślne wartości muszą być ustawione na wszystkich wymaganych polach.",
|
|
1514
|
+
"Step traces": "Ślady kroków",
|
|
1515
|
+
"Please enter a version in the format 'x.y.z' (e.g. 0.0.1 with numbers from 0 to 999) or leave it empty.": "Wprowadź wersję w formacie 'x.y.z' (np. 0.0.1 z liczbami od 0 do 999) lub pozostaw puste.",
|
|
1516
|
+
"Delete unchanged": "Usuń bez zmian",
|
|
1517
|
+
"Delete allocated row if there are no changes.": "Usuń przydzielony wiersz, jeśli nie wprowadzono żadnych zmian.",
|
|
1518
|
+
"Triggers on table": "Wyzwalacze na tabeli",
|
|
1519
|
+
"Please provide the keystore alias and password for the android build.": "Podaj alias i hasło do keystore dla kompilacji Android.",
|
|
1520
|
+
"Submit": "Prześlij",
|
|
1521
|
+
"OK": "OK",
|
|
1522
|
+
"Step settings": "Ustawienia kroku",
|
|
1523
|
+
"Action settings": "Ustawienia akcji",
|
|
1524
|
+
"Workflow": "Przepływ pracy",
|
|
1525
|
+
"Previous runs": "Poprzednie uruchomienia",
|
|
1526
|
+
"The workflow the user will be interacting with.": "Przepływ pracy, z którym użytkownik będzie wchodził w interakcję."
|
|
1512
1527
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1-beta.1",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@aws-sdk/client-s3": "^3.451.0",
|
|
10
|
-
"@saltcorn/base-plugin": "1.1.
|
|
11
|
-
"@saltcorn/builder": "1.1.
|
|
12
|
-
"@saltcorn/data": "1.1.
|
|
13
|
-
"@saltcorn/admin-models": "1.1.
|
|
14
|
-
"@saltcorn/filemanager": "1.1.
|
|
15
|
-
"@saltcorn/markup": "1.1.
|
|
16
|
-
"@saltcorn/plugins-loader": "1.1.
|
|
17
|
-
"@saltcorn/sbadmin2": "1.1.
|
|
10
|
+
"@saltcorn/base-plugin": "1.1.1-beta.1",
|
|
11
|
+
"@saltcorn/builder": "1.1.1-beta.1",
|
|
12
|
+
"@saltcorn/data": "1.1.1-beta.1",
|
|
13
|
+
"@saltcorn/admin-models": "1.1.1-beta.1",
|
|
14
|
+
"@saltcorn/filemanager": "1.1.1-beta.1",
|
|
15
|
+
"@saltcorn/markup": "1.1.1-beta.1",
|
|
16
|
+
"@saltcorn/plugins-loader": "1.1.1-beta.1",
|
|
17
|
+
"@saltcorn/sbadmin2": "1.1.1-beta.1",
|
|
18
18
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
19
|
"@socket.io/sticky": "^1.0.1",
|
|
20
20
|
"adm-zip": "0.5.10",
|
package/public/saltcorn.css
CHANGED
|
@@ -469,6 +469,18 @@ div.unread-notify {
|
|
|
469
469
|
color: inherit;
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
i.add-btw-nodes {
|
|
473
|
+
cursor: pointer;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
pre.mermaid g.node[class*=" wfstep"] {
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
pre.mermaid g.node[class*=" wfadd"] {
|
|
481
|
+
cursor: pointer;
|
|
482
|
+
}
|
|
483
|
+
|
|
472
484
|
.link-style {
|
|
473
485
|
cursor: pointer;
|
|
474
486
|
text-decoration: underline;
|
|
@@ -605,16 +617,18 @@ button.monospace-copy-btn {
|
|
|
605
617
|
display: block;
|
|
606
618
|
}
|
|
607
619
|
|
|
608
|
-
i[class^="unicode-"],
|
|
620
|
+
i[class^="unicode-"],
|
|
621
|
+
i[class*=" unicode-"] {
|
|
609
622
|
font-style: normal;
|
|
610
623
|
}
|
|
611
624
|
|
|
612
|
-
.tabulator.table-dark:not(.thead-light) .tabulator-footer,
|
|
625
|
+
.tabulator.table-dark:not(.thead-light) .tabulator-footer,
|
|
626
|
+
.tabulator.table-dark:not(.thead-light) .tabulator-footer .tabulator-col {
|
|
613
627
|
background-color: #212529;
|
|
614
628
|
border-color: #32383e;
|
|
615
629
|
color: #fff;
|
|
616
630
|
}
|
|
617
631
|
|
|
618
632
|
.mobile-toast-margin {
|
|
619
|
-
margin-bottom:
|
|
620
|
-
}
|
|
633
|
+
margin-bottom: 1rem;
|
|
634
|
+
}
|
package/routes/actions.js
CHANGED
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
addOnDoneRedirect,
|
|
12
12
|
is_relative_url,
|
|
13
13
|
} = require("./utils.js");
|
|
14
|
-
const { ppVal } = require("@saltcorn/data/utils");
|
|
14
|
+
const { ppVal, jsIdentifierValidator } = require("@saltcorn/data/utils");
|
|
15
15
|
const { getState } = require("@saltcorn/data/db/state");
|
|
16
16
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
17
17
|
const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
|
|
@@ -22,6 +22,8 @@ const WorkflowRun = require("@saltcorn/data/models/workflow_run");
|
|
|
22
22
|
const WorkflowTrace = require("@saltcorn/data/models/workflow_trace");
|
|
23
23
|
const Tag = require("@saltcorn/data/models/tag");
|
|
24
24
|
const db = require("@saltcorn/data/db");
|
|
25
|
+
const MarkdownIt = require("markdown-it"),
|
|
26
|
+
md = new MarkdownIt();
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* @type {object}
|
|
@@ -493,7 +495,7 @@ router.post(
|
|
|
493
495
|
function genWorkflowDiagram(steps) {
|
|
494
496
|
const stepNames = steps.map((s) => s.name);
|
|
495
497
|
const nodeLines = steps.map(
|
|
496
|
-
(s) => ` ${s.
|
|
498
|
+
(s) => ` ${s.mmname}["\`**${s.name}**
|
|
497
499
|
${s.action_name}\`"]:::wfstep${s.id}`
|
|
498
500
|
);
|
|
499
501
|
|
|
@@ -501,18 +503,39 @@ function genWorkflowDiagram(steps) {
|
|
|
501
503
|
const linkLines = [];
|
|
502
504
|
let step_ix = 0;
|
|
503
505
|
for (const step of steps) {
|
|
504
|
-
if (step.initial_step) linkLines.push(` _Start --> ${step.
|
|
505
|
-
if (step.
|
|
506
|
+
if (step.initial_step) linkLines.push(` _Start --> ${step.mmname}`);
|
|
507
|
+
if (stepNames.includes(step.next_step)) {
|
|
506
508
|
linkLines.push(
|
|
507
|
-
` ${step.
|
|
509
|
+
` ${step.mmname}-- <i class="fas fa-plus add-btw-nodes btw-nodes-${step.id}-${step.next_step}"></i> ---${step.mmnext}`
|
|
508
510
|
);
|
|
509
|
-
} else if (stepNames.includes(step.next_step)) {
|
|
510
|
-
linkLines.push(` ${step.name} --> ${step.next_step}`);
|
|
511
511
|
} else if (step.next_step) {
|
|
512
|
+
let found = false;
|
|
512
513
|
for (const otherStep of stepNames)
|
|
513
|
-
if (step.next_step.includes(otherStep))
|
|
514
|
-
linkLines.push(
|
|
514
|
+
if (step.next_step.includes(otherStep)) {
|
|
515
|
+
linkLines.push(
|
|
516
|
+
` ${step.mmname} --> ${WorkflowStep.mmescape(otherStep)}`
|
|
517
|
+
);
|
|
518
|
+
found = true;
|
|
519
|
+
}
|
|
520
|
+
if (!found) {
|
|
521
|
+
linkLines.push(
|
|
522
|
+
` ${step.mmname}-- <a href="/actions/stepedit/${step.trigger_id}/${step.id}">Error: missing next step in ${step.mmname}</a> ---_End_${step.mmname}`
|
|
523
|
+
);
|
|
524
|
+
nodeLines.push(
|
|
525
|
+
` _End_${step.mmname}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
} else if (!step.next_step) {
|
|
529
|
+
linkLines.push(` ${step.mmname} --> _End_${step.mmname}`);
|
|
530
|
+
nodeLines.push(
|
|
531
|
+
` _End_${step.mmname}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
|
|
532
|
+
);
|
|
515
533
|
}
|
|
534
|
+
if (step.action_name === "ForLoop") {
|
|
535
|
+
linkLines.push(
|
|
536
|
+
` ${step.mmname}-.->${WorkflowStep.mmescape(step.configuration.loop_body_initial_step)}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
516
539
|
if (step.action_name === "EndForLoop") {
|
|
517
540
|
// TODO this is not correct. improve.
|
|
518
541
|
let forStep;
|
|
@@ -522,12 +545,20 @@ function genWorkflowDiagram(steps) {
|
|
|
522
545
|
break;
|
|
523
546
|
}
|
|
524
547
|
}
|
|
525
|
-
if (forStep) linkLines.push(` ${step.
|
|
548
|
+
if (forStep) linkLines.push(` ${step.mmname} --> ${forStep.mmname}`);
|
|
526
549
|
}
|
|
527
550
|
step_ix += 1;
|
|
528
551
|
}
|
|
552
|
+
if (!steps.length || !steps.find((s) => s.initial_step)) {
|
|
553
|
+
linkLines.push(` _Start --> _End`);
|
|
554
|
+
nodeLines.push(
|
|
555
|
+
` _End:::wfaddstart@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
529
558
|
const fc =
|
|
530
559
|
"flowchart TD\n" + nodeLines.join("\n") + "\n" + linkLines.join("\n");
|
|
560
|
+
//console.log(fc);
|
|
561
|
+
|
|
531
562
|
return fc;
|
|
532
563
|
}
|
|
533
564
|
|
|
@@ -553,7 +584,29 @@ const getWorkflowConfig = async (req, id, table, trigger) => {
|
|
|
553
584
|
],
|
|
554
585
|
});
|
|
555
586
|
trigCfgForm.values = trigger.configuration;
|
|
587
|
+
let copilot_form = "";
|
|
588
|
+
|
|
589
|
+
if (getState().functions.copilot_generate_workflow) {
|
|
590
|
+
copilot_form = renderForm(
|
|
591
|
+
new Form({
|
|
592
|
+
action: `/actions/gen-copilot/${id}`,
|
|
593
|
+
values: { description: trigger.description || "" },
|
|
594
|
+
submitLabel: "Generate workflow with copilot",
|
|
595
|
+
formStyle: "vert",
|
|
596
|
+
fields: [
|
|
597
|
+
{
|
|
598
|
+
name: "description",
|
|
599
|
+
label: "Description",
|
|
600
|
+
type: "String",
|
|
601
|
+
fieldview: "textarea",
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
}),
|
|
605
|
+
req.csrfToken()
|
|
606
|
+
);
|
|
607
|
+
}
|
|
556
608
|
return (
|
|
609
|
+
copilot_form +
|
|
557
610
|
pre({ class: "mermaid" }, genWorkflowDiagram(steps)) +
|
|
558
611
|
script(
|
|
559
612
|
{ defer: "defer" },
|
|
@@ -561,13 +614,31 @@ const getWorkflowConfig = async (req, id, table, trigger) => {
|
|
|
561
614
|
const ns = $("g.node");
|
|
562
615
|
if(!ns.length) setTimeout(tryAddWFNodes, 200)
|
|
563
616
|
else {
|
|
617
|
+
$("i.add-btw-nodes").on("click", (e)=>{
|
|
618
|
+
const $e = $(e.target || e);
|
|
619
|
+
const cls = $e.attr('class');
|
|
620
|
+
const idnext = cls.split(" ").find(c=>c.startsWith("btw-nodes-")).
|
|
621
|
+
substr(10);
|
|
622
|
+
const [idprev, nmnext] = idnext.split("-");
|
|
623
|
+
location.href = '/actions/stepedit/${trigger.id}?after_step='+idprev+'&before_step='+nmnext;
|
|
624
|
+
})
|
|
564
625
|
$("g.node").on("click", (e)=>{
|
|
565
626
|
const $e = $(e.target || e).closest("g.node")
|
|
566
627
|
const cls = $e.attr('class')
|
|
567
|
-
if(!cls
|
|
628
|
+
if(!cls) return;
|
|
629
|
+
//console.log(cls)
|
|
630
|
+
if(cls.includes("wfstep")) {
|
|
568
631
|
const id = cls.split(" ").find(c=>c.startsWith("wfstep")).
|
|
569
632
|
substr(6);
|
|
570
633
|
location.href = '/actions/stepedit/${trigger.id}/'+id;
|
|
634
|
+
}
|
|
635
|
+
if(cls.includes("wfaddstart")) {
|
|
636
|
+
location.href = '/actions/stepedit/${trigger.id}?initial_step=true';
|
|
637
|
+
} else if(cls.includes("wfadd")) {
|
|
638
|
+
const id = cls.split(" ").find(c=>c.startsWith("wfadd")).
|
|
639
|
+
substr(5);
|
|
640
|
+
location.href = '/actions/stepedit/${trigger.id}?after_step='+id;
|
|
641
|
+
}
|
|
571
642
|
//console.log($e.attr('class'), id)
|
|
572
643
|
})
|
|
573
644
|
}
|
|
@@ -579,7 +650,7 @@ window.addEventListener('DOMContentLoaded',tryAddWFNodes)`
|
|
|
579
650
|
href: `/actions/stepedit/${trigger.id}${
|
|
580
651
|
initial_step ? "" : "?initial_step=true"
|
|
581
652
|
}`,
|
|
582
|
-
class: "btn btn-
|
|
653
|
+
class: "btn btn-secondary",
|
|
583
654
|
},
|
|
584
655
|
i({ class: "fas fa-plus me-2" }),
|
|
585
656
|
"Add step"
|
|
@@ -595,17 +666,13 @@ window.addEventListener('DOMContentLoaded',tryAddWFNodes)`
|
|
|
595
666
|
);
|
|
596
667
|
};
|
|
597
668
|
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (badc) return `Character ${badc} not allowed`;
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
669
|
+
const getWorkflowStepForm = async (
|
|
670
|
+
trigger,
|
|
671
|
+
req,
|
|
672
|
+
step_id,
|
|
673
|
+
after_step,
|
|
674
|
+
before_step
|
|
675
|
+
) => {
|
|
609
676
|
const table = trigger.table_id ? Table.findOne(trigger.table_id) : null;
|
|
610
677
|
const actionExplainers = {};
|
|
611
678
|
|
|
@@ -626,30 +693,34 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
626
693
|
});
|
|
627
694
|
|
|
628
695
|
for (const field of cfgFields) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
696
|
+
let cfgFld;
|
|
697
|
+
if (field.isRepeat)
|
|
698
|
+
cfgFld = new FieldRepeat({
|
|
699
|
+
...field,
|
|
700
|
+
showIf: {
|
|
701
|
+
wf_action_name: name,
|
|
702
|
+
...(field.showIf || {}),
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
else
|
|
706
|
+
cfgFld = {
|
|
707
|
+
...field,
|
|
708
|
+
showIf: {
|
|
709
|
+
wf_action_name: name,
|
|
710
|
+
...(field.showIf || {}),
|
|
711
|
+
},
|
|
712
|
+
};
|
|
636
713
|
if (cfgFld.input_type === "code") cfgFld.input_type = "textarea";
|
|
637
714
|
actionConfigFields.push(cfgFld);
|
|
638
715
|
}
|
|
639
716
|
} catch {}
|
|
640
717
|
}
|
|
718
|
+
const builtInActionExplainers = WorkflowStep.builtInActionExplainers();
|
|
641
719
|
const actionsNotRequiringRow = Trigger.action_options({
|
|
642
720
|
notRequireRow: true,
|
|
643
721
|
noMultiStep: true,
|
|
644
722
|
builtInLabel: "Workflow Actions",
|
|
645
|
-
builtIns:
|
|
646
|
-
"SetContext",
|
|
647
|
-
"TableQuery",
|
|
648
|
-
"Output",
|
|
649
|
-
"WaitUntil",
|
|
650
|
-
"WaitNextTick",
|
|
651
|
-
"UserForm",
|
|
652
|
-
],
|
|
723
|
+
builtIns: Object.keys(builtInActionExplainers),
|
|
653
724
|
forWorkflow: true,
|
|
654
725
|
});
|
|
655
726
|
const triggers = Trigger.find({
|
|
@@ -658,138 +729,13 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
658
729
|
triggers.forEach((tr) => {
|
|
659
730
|
if (tr.description) actionExplainers[tr.name] = tr.description;
|
|
660
731
|
});
|
|
661
|
-
actionExplainers
|
|
662
|
-
|
|
663
|
-
actionExplainers.Output =
|
|
664
|
-
"Display a message to the user. Pause workflow until the message is read.";
|
|
665
|
-
actionExplainers.WaitUntil = "Pause until a time in the future";
|
|
666
|
-
actionExplainers.WaitNextTick =
|
|
667
|
-
"Pause until the next scheduler invocation (at most 5 minutes)";
|
|
668
|
-
actionExplainers.UserForm =
|
|
669
|
-
"Ask a user one or more questions, pause until they are answered";
|
|
670
|
-
|
|
671
|
-
actionConfigFields.push({
|
|
672
|
-
label: "Form header",
|
|
673
|
-
sublabel: "Text shown to the user at the top of the form",
|
|
674
|
-
name: "form_header",
|
|
675
|
-
type: "String",
|
|
676
|
-
showIf: { wf_action_name: "UserForm" },
|
|
677
|
-
});
|
|
678
|
-
actionConfigFields.push({
|
|
679
|
-
label: "User ID",
|
|
680
|
-
name: "user_id_expression",
|
|
681
|
-
type: "String",
|
|
682
|
-
sublabel: "Optional. If blank assigned to user starting the workflow",
|
|
683
|
-
showIf: { wf_action_name: "UserForm" },
|
|
684
|
-
});
|
|
685
|
-
actionConfigFields.push({
|
|
686
|
-
label: "Resume at",
|
|
687
|
-
name: "resume_at",
|
|
688
|
-
sublabel:
|
|
689
|
-
"JavaScript expression for the time to resume. <code>moment</code> is in scope.",
|
|
690
|
-
type: "String",
|
|
691
|
-
showIf: { wf_action_name: "WaitUntil" },
|
|
692
|
-
});
|
|
693
|
-
actionConfigFields.push({
|
|
694
|
-
label: "Context values",
|
|
695
|
-
name: "ctx_values",
|
|
696
|
-
sublabel:
|
|
697
|
-
"JavaScript object expression for the variables to set. Example <code>{x: 5, y:y+1}</code> will set x to 5 and increment existing context variable y",
|
|
698
|
-
type: "String",
|
|
699
|
-
fieldview: "textarea",
|
|
700
|
-
class: "validate-expression",
|
|
701
|
-
default: "{}",
|
|
702
|
-
showIf: { wf_action_name: "SetContext" },
|
|
703
|
-
});
|
|
704
|
-
actionConfigFields.push({
|
|
705
|
-
label: "Output text",
|
|
706
|
-
name: "output_text",
|
|
707
|
-
sublabel:
|
|
708
|
-
"Message shown to the user. Can contain HTML tags and use interpolations {{ }} to access the context",
|
|
709
|
-
type: "String",
|
|
710
|
-
fieldview: "textarea",
|
|
711
|
-
showIf: { wf_action_name: "Output" },
|
|
712
|
-
});
|
|
713
|
-
actionConfigFields.push({
|
|
714
|
-
label: "Table",
|
|
715
|
-
name: "query_table",
|
|
716
|
-
type: "String",
|
|
717
|
-
required: true,
|
|
718
|
-
attributes: { options: (await Table.find()).map((t) => t.name) },
|
|
719
|
-
showIf: { wf_action_name: "TableQuery" },
|
|
720
|
-
});
|
|
721
|
-
actionConfigFields.push({
|
|
722
|
-
label: "Query",
|
|
723
|
-
name: "query_object",
|
|
724
|
-
sublabel: "Where object, example <code>{manager: 1}</code>",
|
|
725
|
-
type: "String",
|
|
726
|
-
required: true,
|
|
727
|
-
class: "validate-expression",
|
|
728
|
-
default: "{}",
|
|
729
|
-
showIf: { wf_action_name: "TableQuery" },
|
|
730
|
-
});
|
|
731
|
-
actionConfigFields.push({
|
|
732
|
-
label: "Variable",
|
|
733
|
-
name: "query_variable",
|
|
734
|
-
sublabel: "Context variable to write to query results to",
|
|
735
|
-
type: "String",
|
|
736
|
-
required: true,
|
|
737
|
-
validator: jsIdentifierValidator,
|
|
738
|
-
showIf: { wf_action_name: "TableQuery" },
|
|
739
|
-
});
|
|
740
|
-
actionConfigFields.push(
|
|
741
|
-
new FieldRepeat({
|
|
742
|
-
name: "user_form_questions",
|
|
743
|
-
showIf: { wf_action_name: "UserForm" },
|
|
744
|
-
fields: [
|
|
745
|
-
{
|
|
746
|
-
label: "Label",
|
|
747
|
-
name: "label",
|
|
748
|
-
type: "String",
|
|
749
|
-
sublabel:
|
|
750
|
-
"The text that will shown to the user above the input elements",
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
label: "Variable name",
|
|
754
|
-
name: "var_name",
|
|
755
|
-
type: "String",
|
|
756
|
-
sublabel:
|
|
757
|
-
"The answer will be set in the context with this variable name",
|
|
758
|
-
validator: jsIdentifierValidator,
|
|
759
|
-
},
|
|
760
|
-
{
|
|
761
|
-
label: "Input Type",
|
|
762
|
-
name: "qtype",
|
|
763
|
-
type: "String",
|
|
764
|
-
required: true,
|
|
765
|
-
attributes: {
|
|
766
|
-
options: [
|
|
767
|
-
"Yes/No",
|
|
768
|
-
"Checkbox",
|
|
769
|
-
"Free text",
|
|
770
|
-
"Multiple choice",
|
|
771
|
-
//"Multiple checks",
|
|
772
|
-
"Integer",
|
|
773
|
-
"Float",
|
|
774
|
-
//"File upload",
|
|
775
|
-
],
|
|
776
|
-
},
|
|
777
|
-
},
|
|
778
|
-
{
|
|
779
|
-
label: "Options",
|
|
780
|
-
name: "options",
|
|
781
|
-
type: "String",
|
|
782
|
-
sublabel: "Comma separated list of multiple choice options",
|
|
783
|
-
showIf: { qtype: ["Multiple choice", "Multiple checks"] },
|
|
784
|
-
},
|
|
785
|
-
],
|
|
786
|
-
})
|
|
787
|
-
);
|
|
732
|
+
Object.assign(actionExplainers, builtInActionExplainers);
|
|
733
|
+
actionConfigFields.push(...(await WorkflowStep.builtInActionConfigFields()));
|
|
788
734
|
|
|
789
735
|
const form = new Form({
|
|
790
736
|
action: addOnDoneRedirect(`/actions/stepedit/${trigger.id}`, req),
|
|
791
|
-
onChange: "saveAndContinueIfValid(this)",
|
|
792
|
-
submitLabel: req.__("Done"),
|
|
737
|
+
onChange: step_id ? "saveAndContinueIfValid(this)" : undefined,
|
|
738
|
+
submitLabel: step_id ? req.__("Done") : undefined,
|
|
793
739
|
additionalButtons: step_id
|
|
794
740
|
? [
|
|
795
741
|
{
|
|
@@ -856,6 +802,9 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
|
|
|
856
802
|
],
|
|
857
803
|
});
|
|
858
804
|
form.hidden("wf_step_id");
|
|
805
|
+
form.hidden("_after_step");
|
|
806
|
+
if (before_step) form.values.wf_next_step = before_step;
|
|
807
|
+
if (after_step) form.values._after_step = after_step;
|
|
859
808
|
if (step_id) {
|
|
860
809
|
const step = await WorkflowStep.findOne({ id: step_id });
|
|
861
810
|
if (!step) throw new Error("Step not found");
|
|
@@ -1366,9 +1315,15 @@ router.get(
|
|
|
1366
1315
|
isAdmin,
|
|
1367
1316
|
error_catcher(async (req, res) => {
|
|
1368
1317
|
const { trigger_id, step_id } = req.params;
|
|
1369
|
-
const { initial_step,
|
|
1318
|
+
const { initial_step, after_step, before_step } = req.query;
|
|
1370
1319
|
const trigger = await Trigger.findOne({ id: trigger_id });
|
|
1371
|
-
const form = await getWorkflowStepForm(
|
|
1320
|
+
const form = await getWorkflowStepForm(
|
|
1321
|
+
trigger,
|
|
1322
|
+
req,
|
|
1323
|
+
step_id,
|
|
1324
|
+
after_step,
|
|
1325
|
+
before_step
|
|
1326
|
+
);
|
|
1372
1327
|
|
|
1373
1328
|
if (initial_step) form.values.wf_initial_step = true;
|
|
1374
1329
|
if (!step_id) {
|
|
@@ -1434,6 +1389,7 @@ router.post(
|
|
|
1434
1389
|
wf_initial_step,
|
|
1435
1390
|
wf_only_if,
|
|
1436
1391
|
wf_step_id,
|
|
1392
|
+
_after_step,
|
|
1437
1393
|
...configuration
|
|
1438
1394
|
} = form.values;
|
|
1439
1395
|
Object.entries(configuration).forEach(([k, v]) => {
|
|
@@ -1469,6 +1425,13 @@ router.post(
|
|
|
1469
1425
|
res.redirect(`/actions/configure/${step.trigger_id}`);
|
|
1470
1426
|
}
|
|
1471
1427
|
}
|
|
1428
|
+
if (_after_step && _after_step !== "undefined") {
|
|
1429
|
+
const astep = await WorkflowStep.findOne({
|
|
1430
|
+
id: _after_step,
|
|
1431
|
+
trigger_id,
|
|
1432
|
+
});
|
|
1433
|
+
if (astep) await astep.update({ next_step: step.name });
|
|
1434
|
+
}
|
|
1472
1435
|
} catch (e) {
|
|
1473
1436
|
const emsg =
|
|
1474
1437
|
e.message ===
|
|
@@ -1484,6 +1447,28 @@ router.post(
|
|
|
1484
1447
|
})
|
|
1485
1448
|
);
|
|
1486
1449
|
|
|
1450
|
+
router.post(
|
|
1451
|
+
"/gen-copilot/:trigger_id",
|
|
1452
|
+
isAdmin,
|
|
1453
|
+
error_catcher(async (req, res) => {
|
|
1454
|
+
const { trigger_id } = req.params;
|
|
1455
|
+
const trigger = await Trigger.findOne({ id: trigger_id });
|
|
1456
|
+
await WorkflowStep.deleteForTrigger(trigger.id);
|
|
1457
|
+
const description = req.body.description;
|
|
1458
|
+
await Trigger.update(trigger.id, { description });
|
|
1459
|
+
const steps = await getState().functions.copilot_generate_workflow.run(
|
|
1460
|
+
description,
|
|
1461
|
+
trigger.id
|
|
1462
|
+
);
|
|
1463
|
+
if (steps.length) steps[0].initial_step = true;
|
|
1464
|
+
for (const step of steps) {
|
|
1465
|
+
step.trigger_id = trigger.id;
|
|
1466
|
+
await WorkflowStep.create(step);
|
|
1467
|
+
}
|
|
1468
|
+
res.redirect(`/actions/configure/${trigger.id}`);
|
|
1469
|
+
})
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1487
1472
|
router.post(
|
|
1488
1473
|
"/delete-step/:step_id",
|
|
1489
1474
|
isAdmin,
|
|
@@ -1514,6 +1499,10 @@ router.get(
|
|
|
1514
1499
|
[
|
|
1515
1500
|
{ label: "Trigger", key: (run) => trNames[run.trigger_id] },
|
|
1516
1501
|
{ label: "Started", key: (run) => localeDateTime(run.started_at) },
|
|
1502
|
+
{
|
|
1503
|
+
label: "Updated",
|
|
1504
|
+
key: (run) => localeDateTime(run.status_updated_at),
|
|
1505
|
+
},
|
|
1517
1506
|
{ label: "Status", key: "status" },
|
|
1518
1507
|
{
|
|
1519
1508
|
label: "",
|
|
@@ -1698,10 +1687,12 @@ router.post(
|
|
|
1698
1687
|
);
|
|
1699
1688
|
|
|
1700
1689
|
const getWorkflowStepUserForm = async (run, trigger, step, req) => {
|
|
1690
|
+
let blurb = run.wait_info.output || step.configuration?.form_header || "";
|
|
1691
|
+
if (run.wait_info.markdown && run.wait_info.output) blurb = md.render(blurb);
|
|
1701
1692
|
const form = new Form({
|
|
1702
1693
|
action: `/actions/fill-workflow-form/${run.id}`,
|
|
1703
1694
|
submitLabel: run.wait_info.output ? req.__("OK") : req.__("Submit"),
|
|
1704
|
-
blurb
|
|
1695
|
+
blurb,
|
|
1705
1696
|
formStyle: run.wait_info.output || req.xhr ? "vert" : undefined,
|
|
1706
1697
|
fields: await run.userFormFields(step),
|
|
1707
1698
|
});
|
package/routes/utils.js
CHANGED
package/tests/view.test.js
CHANGED
|
@@ -194,6 +194,43 @@ describe("render view with slug", () => {
|
|
|
194
194
|
});
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
describe("frozen user object", () => {
|
|
198
|
+
it("should create view writing to user object", async () => {
|
|
199
|
+
const table = Table.findOne({ name: "books" });
|
|
200
|
+
const configuration = {
|
|
201
|
+
layout: {
|
|
202
|
+
type: "container",
|
|
203
|
+
style: {},
|
|
204
|
+
contents: {
|
|
205
|
+
type: "blank",
|
|
206
|
+
contents: "'userid='+user.id",
|
|
207
|
+
isFormula: {
|
|
208
|
+
text: true,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
showIfFormula: "user.id=1",
|
|
212
|
+
},
|
|
213
|
+
columns: [],
|
|
214
|
+
};
|
|
215
|
+
await View.create({
|
|
216
|
+
table_id: table.id,
|
|
217
|
+
name: "ShowBookWriteUser",
|
|
218
|
+
viewtemplate: "Show",
|
|
219
|
+
configuration,
|
|
220
|
+
min_role: 100,
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
it("should run view setting user", async () => {
|
|
224
|
+
const loginCookie = await getStaffLoginCookie();
|
|
225
|
+
|
|
226
|
+
const app = await getApp({ disableCsrf: true });
|
|
227
|
+
await request(app)
|
|
228
|
+
.get("/view/ShowBookWriteUser?id=1")
|
|
229
|
+
.set("Cookie", loginCookie)
|
|
230
|
+
.expect(toInclude(">userid=2</div>"));
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
197
234
|
describe("action row_variable", () => {
|
|
198
235
|
const createFilterView = async ({
|
|
199
236
|
configuration,
|