@saltcorn/server 1.1.0 → 1.1.1-beta.0

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 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/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "1.1.0",
3
+ "version": "1.1.1-beta.0",
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.0",
11
- "@saltcorn/builder": "1.1.0",
12
- "@saltcorn/data": "1.1.0",
13
- "@saltcorn/admin-models": "1.1.0",
14
- "@saltcorn/filemanager": "1.1.0",
15
- "@saltcorn/markup": "1.1.0",
16
- "@saltcorn/plugins-loader": "1.1.0",
17
- "@saltcorn/sbadmin2": "1.1.0",
10
+ "@saltcorn/base-plugin": "1.1.1-beta.0",
11
+ "@saltcorn/builder": "1.1.1-beta.0",
12
+ "@saltcorn/data": "1.1.1-beta.0",
13
+ "@saltcorn/admin-models": "1.1.1-beta.0",
14
+ "@saltcorn/filemanager": "1.1.1-beta.0",
15
+ "@saltcorn/markup": "1.1.1-beta.0",
16
+ "@saltcorn/plugins-loader": "1.1.1-beta.0",
17
+ "@saltcorn/sbadmin2": "1.1.1-beta.0",
18
18
  "@socket.io/cluster-adapter": "^0.2.1",
19
19
  "@socket.io/sticky": "^1.0.1",
20
20
  "adm-zip": "0.5.10",
@@ -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-"], 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, .tabulator.table-dark:not(.thead-light) .tabulator-footer .tabulator-col {
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: 1.0rem
620
- }
633
+ margin-bottom: 1rem;
634
+ }
package/routes/actions.js CHANGED
@@ -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}
@@ -507,11 +509,29 @@ function genWorkflowDiagram(steps) {
507
509
  ` ${step.name} --> ${step.configuration.for_loop_step_name}`
508
510
  );
509
511
  } else if (stepNames.includes(step.next_step)) {
510
- linkLines.push(` ${step.name} --> ${step.next_step}`);
512
+ linkLines.push(
513
+ ` ${step.name}-- <i class="fas fa-plus add-btw-nodes btw-nodes-${step.id}-${step.next_step}"></i> ---${step.next_step}`
514
+ );
511
515
  } else if (step.next_step) {
516
+ let found = false;
512
517
  for (const otherStep of stepNames)
513
- if (step.next_step.includes(otherStep))
518
+ if (step.next_step.includes(otherStep)) {
514
519
  linkLines.push(` ${step.name} --> ${otherStep}`);
520
+ found = true;
521
+ }
522
+ if (!found) {
523
+ linkLines.push(
524
+ ` ${step.name}-- <a href="/actions/stepedit/${step.trigger_id}/${step.id}">Error: missing next step in ${step.name}</a> ---_End_${step.name}`
525
+ );
526
+ nodeLines.push(
527
+ ` _End_${step.name}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
528
+ );
529
+ }
530
+ } else if (!step.next_step) {
531
+ linkLines.push(` ${step.name} --> _End_${step.name}`);
532
+ nodeLines.push(
533
+ ` _End_${step.name}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
534
+ );
515
535
  }
516
536
  if (step.action_name === "EndForLoop") {
517
537
  // TODO this is not correct. improve.
@@ -526,8 +546,16 @@ function genWorkflowDiagram(steps) {
526
546
  }
527
547
  step_ix += 1;
528
548
  }
549
+ if (!steps.length || !steps.find((s) => s.initial_step)) {
550
+ linkLines.push(` _Start --> _End`);
551
+ nodeLines.push(
552
+ ` _End:::wfaddstart@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
553
+ );
554
+ }
529
555
  const fc =
530
556
  "flowchart TD\n" + nodeLines.join("\n") + "\n" + linkLines.join("\n");
557
+ //console.log(fc);
558
+
531
559
  return fc;
532
560
  }
533
561
 
@@ -561,13 +589,31 @@ const getWorkflowConfig = async (req, id, table, trigger) => {
561
589
  const ns = $("g.node");
562
590
  if(!ns.length) setTimeout(tryAddWFNodes, 200)
563
591
  else {
592
+ $("i.add-btw-nodes").on("click", (e)=>{
593
+ const $e = $(e.target || e);
594
+ const cls = $e.attr('class');
595
+ const idnext = cls.split(" ").find(c=>c.startsWith("btw-nodes-")).
596
+ substr(10);
597
+ const [idprev, nmnext] = idnext.split("-");
598
+ location.href = '/actions/stepedit/${trigger.id}?after_step='+idprev+'&before_step='+nmnext;
599
+ })
564
600
  $("g.node").on("click", (e)=>{
565
601
  const $e = $(e.target || e).closest("g.node")
566
602
  const cls = $e.attr('class')
567
- if(!cls || !cls.includes("wfstep")) return;
603
+ if(!cls) return;
604
+ //console.log(cls)
605
+ if(cls.includes("wfstep")) {
568
606
  const id = cls.split(" ").find(c=>c.startsWith("wfstep")).
569
607
  substr(6);
570
608
  location.href = '/actions/stepedit/${trigger.id}/'+id;
609
+ }
610
+ if(cls.includes("wfaddstart")) {
611
+ location.href = '/actions/stepedit/${trigger.id}?initial_step=true';
612
+ } else if(cls.includes("wfadd")) {
613
+ const id = cls.split(" ").find(c=>c.startsWith("wfadd")).
614
+ substr(5);
615
+ location.href = '/actions/stepedit/${trigger.id}?after_step='+id;
616
+ }
571
617
  //console.log($e.attr('class'), id)
572
618
  })
573
619
  }
@@ -579,7 +625,7 @@ window.addEventListener('DOMContentLoaded',tryAddWFNodes)`
579
625
  href: `/actions/stepedit/${trigger.id}${
580
626
  initial_step ? "" : "?initial_step=true"
581
627
  }`,
582
- class: "btn btn-primary",
628
+ class: "btn btn-secondary",
583
629
  },
584
630
  i({ class: "fas fa-plus me-2" }),
585
631
  "Add step"
@@ -605,7 +651,13 @@ const jsIdentifierValidator = (s) => {
605
651
  if (badc) return `Character ${badc} not allowed`;
606
652
  };
607
653
 
608
- const getWorkflowStepForm = async (trigger, req, step_id) => {
654
+ const getWorkflowStepForm = async (
655
+ trigger,
656
+ req,
657
+ step_id,
658
+ after_step,
659
+ before_step
660
+ ) => {
609
661
  const table = trigger.table_id ? Table.findOne(trigger.table_id) : null;
610
662
  const actionExplainers = {};
611
663
 
@@ -626,13 +678,23 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
626
678
  });
627
679
 
628
680
  for (const field of cfgFields) {
629
- const cfgFld = {
630
- ...field,
631
- showIf: {
632
- wf_action_name: name,
633
- ...(field.showIf || {}),
634
- },
635
- };
681
+ let cfgFld;
682
+ if (field.isRepeat)
683
+ cfgFld = new FieldRepeat({
684
+ ...field,
685
+ showIf: {
686
+ wf_action_name: name,
687
+ ...(field.showIf || {}),
688
+ },
689
+ });
690
+ else
691
+ cfgFld = {
692
+ ...field,
693
+ showIf: {
694
+ wf_action_name: name,
695
+ ...(field.showIf || {}),
696
+ },
697
+ };
636
698
  if (cfgFld.input_type === "code") cfgFld.input_type = "textarea";
637
699
  actionConfigFields.push(cfgFld);
638
700
  }
@@ -646,6 +708,7 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
646
708
  "SetContext",
647
709
  "TableQuery",
648
710
  "Output",
711
+ "DataOutput",
649
712
  "WaitUntil",
650
713
  "WaitNextTick",
651
714
  "UserForm",
@@ -662,6 +725,8 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
662
725
  actionExplainers.TableQuery = "Query a table into a variable in the context";
663
726
  actionExplainers.Output =
664
727
  "Display a message to the user. Pause workflow until the message is read.";
728
+ actionExplainers.DataOutput =
729
+ "Display a value to the user. Arrays of objects will be displayed as tables. Pause workflow until the message is read.";
665
730
  actionExplainers.WaitUntil = "Pause until a time in the future";
666
731
  actionExplainers.WaitNextTick =
667
732
  "Pause until the next scheduler invocation (at most 5 minutes)";
@@ -710,6 +775,23 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
710
775
  fieldview: "textarea",
711
776
  showIf: { wf_action_name: "Output" },
712
777
  });
778
+ actionConfigFields.push({
779
+ label: "Output expression",
780
+ name: "output_expr",
781
+ sublabel:
782
+ "JavaScript expression for the value to output. Typically the name of a variable",
783
+ type: "String",
784
+ class: "validate-expression",
785
+ showIf: { wf_action_name: "DataOutput" },
786
+ });
787
+ actionConfigFields.push({
788
+ label: "Markdown",
789
+ name: "markdown",
790
+ sublabel:
791
+ "The centents are markdown formatted and should be rendered to HTML",
792
+ type: "Bool",
793
+ showIf: { wf_action_name: "Output" },
794
+ });
713
795
  actionConfigFields.push({
714
796
  label: "Table",
715
797
  name: "query_table",
@@ -788,8 +870,8 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
788
870
 
789
871
  const form = new Form({
790
872
  action: addOnDoneRedirect(`/actions/stepedit/${trigger.id}`, req),
791
- onChange: "saveAndContinueIfValid(this)",
792
- submitLabel: req.__("Done"),
873
+ onChange: step_id ? "saveAndContinueIfValid(this)" : undefined,
874
+ submitLabel: step_id ? req.__("Done") : undefined,
793
875
  additionalButtons: step_id
794
876
  ? [
795
877
  {
@@ -856,6 +938,9 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
856
938
  ],
857
939
  });
858
940
  form.hidden("wf_step_id");
941
+ form.hidden("_after_step");
942
+ if (before_step) form.values.wf_next_step = before_step;
943
+ if (after_step) form.values._after_step = after_step;
859
944
  if (step_id) {
860
945
  const step = await WorkflowStep.findOne({ id: step_id });
861
946
  if (!step) throw new Error("Step not found");
@@ -1366,9 +1451,15 @@ router.get(
1366
1451
  isAdmin,
1367
1452
  error_catcher(async (req, res) => {
1368
1453
  const { trigger_id, step_id } = req.params;
1369
- const { initial_step, name } = req.query;
1454
+ const { initial_step, after_step, before_step } = req.query;
1370
1455
  const trigger = await Trigger.findOne({ id: trigger_id });
1371
- const form = await getWorkflowStepForm(trigger, req, step_id);
1456
+ const form = await getWorkflowStepForm(
1457
+ trigger,
1458
+ req,
1459
+ step_id,
1460
+ after_step,
1461
+ before_step
1462
+ );
1372
1463
 
1373
1464
  if (initial_step) form.values.wf_initial_step = true;
1374
1465
  if (!step_id) {
@@ -1434,6 +1525,7 @@ router.post(
1434
1525
  wf_initial_step,
1435
1526
  wf_only_if,
1436
1527
  wf_step_id,
1528
+ _after_step,
1437
1529
  ...configuration
1438
1530
  } = form.values;
1439
1531
  Object.entries(configuration).forEach(([k, v]) => {
@@ -1469,6 +1561,13 @@ router.post(
1469
1561
  res.redirect(`/actions/configure/${step.trigger_id}`);
1470
1562
  }
1471
1563
  }
1564
+ if (_after_step) {
1565
+ const astep = await WorkflowStep.findOne({
1566
+ id: _after_step,
1567
+ trigger_id,
1568
+ });
1569
+ if (astep) await astep.update({ next_step: step.name });
1570
+ }
1472
1571
  } catch (e) {
1473
1572
  const emsg =
1474
1573
  e.message ===
@@ -1514,6 +1613,10 @@ router.get(
1514
1613
  [
1515
1614
  { label: "Trigger", key: (run) => trNames[run.trigger_id] },
1516
1615
  { label: "Started", key: (run) => localeDateTime(run.started_at) },
1616
+ {
1617
+ label: "Updated",
1618
+ key: (run) => localeDateTime(run.status_updated_at),
1619
+ },
1517
1620
  { label: "Status", key: "status" },
1518
1621
  {
1519
1622
  label: "",
@@ -1698,10 +1801,12 @@ router.post(
1698
1801
  );
1699
1802
 
1700
1803
  const getWorkflowStepUserForm = async (run, trigger, step, req) => {
1804
+ let blurb = run.wait_info.output || step.configuration?.form_header || "";
1805
+ if (run.wait_info.markdown && run.wait_info.output) blurb = md.render(blurb);
1701
1806
  const form = new Form({
1702
1807
  action: `/actions/fill-workflow-form/${run.id}`,
1703
1808
  submitLabel: run.wait_info.output ? req.__("OK") : req.__("Submit"),
1704
- blurb: run.wait_info.output || step.configuration?.form_header || "",
1809
+ blurb,
1705
1810
  formStyle: run.wait_info.output || req.xhr ? "vert" : undefined,
1706
1811
  fields: await run.userFormFields(step),
1707
1812
  });