@saltcorn/server 1.1.0-beta.14 → 1.1.0-beta.15

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.
@@ -1208,6 +1208,21 @@ function check_unsaved_form(event, script_tag) {
1208
1208
  event.returnValue = true;
1209
1209
  }
1210
1210
  }
1211
+ function check_delete_unsaved(tablename, script_tag) {
1212
+ const form = $(script_tag).parent().find("form");
1213
+ if (form.length && !form.attr("data-form-changed")) {
1214
+ //delete row
1215
+ const rec = get_form_record(form);
1216
+
1217
+ $.ajax({
1218
+ url: `/api/${tablename}/${rec.id}`,
1219
+ type: "DELETE",
1220
+ headers: {
1221
+ "CSRF-Token": _sc_globalCsrf,
1222
+ },
1223
+ });
1224
+ }
1225
+ }
1211
1226
 
1212
1227
  (() => {
1213
1228
  const e = document.querySelector("[data-sidebar-toggler]");
package/routes/actions.js CHANGED
@@ -344,6 +344,11 @@ router.get(
344
344
  isAdmin,
345
345
  error_catcher(async (req, res) => {
346
346
  const form = await triggerForm(req);
347
+ if (req.query.table) {
348
+ const table = Table.findOne({ name: req.query.table });
349
+ if (table) form.values.table_id = table.id;
350
+ }
351
+
347
352
  send_events_page({
348
353
  res,
349
354
  req,
@@ -487,15 +492,16 @@ router.post(
487
492
 
488
493
  function genWorkflowDiagram(steps) {
489
494
  const stepNames = steps.map((s) => s.name);
490
- const nodeLines = steps
491
- .map(
492
- (s) => ` ${s.name}["\`**${s.name}**
495
+ const nodeLines = steps.map(
496
+ (s) => ` ${s.name}["\`**${s.name}**
493
497
  ${s.action_name}\`"]:::wfstep${s.id}`
494
- )
495
- .join("\n");
498
+ );
499
+
500
+ nodeLines.unshift(` _Start@{ shape: circle, label: "Start" }`);
496
501
  const linkLines = [];
497
502
  let step_ix = 0;
498
503
  for (const step of steps) {
504
+ if (step.initial_step) linkLines.push(` _Start --> ${step.name}`);
499
505
  if (step.action_name === "ForLoop") {
500
506
  linkLines.push(
501
507
  ` ${step.name} --> ${step.configuration.for_loop_step_name}`
@@ -520,7 +526,9 @@ function genWorkflowDiagram(steps) {
520
526
  }
521
527
  step_ix += 1;
522
528
  }
523
- return "flowchart TD\n" + nodeLines + "\n" + linkLines.join("\n");
529
+ const fc =
530
+ "flowchart TD\n" + nodeLines.join("\n") + "\n" + linkLines.join("\n");
531
+ return fc;
524
532
  }
525
533
 
526
534
  const getWorkflowConfig = async (req, id, table, trigger) => {
@@ -644,6 +652,7 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
644
652
  builtIns: [
645
653
  "SetContext",
646
654
  "TableQuery",
655
+ "Output",
647
656
  "WaitUntil",
648
657
  "UserForm",
649
658
  "WaitNextTick",
@@ -683,7 +692,15 @@ const getWorkflowStepForm = async (trigger, req, step_id) => {
683
692
  default: "{}",
684
693
  showIf: { wf_action_name: "SetContext" },
685
694
  });
686
-
695
+ actionConfigFields.push({
696
+ label: "Output text",
697
+ name: "output_text",
698
+ sublabel:
699
+ "Message shown to the user. Can contain HTML tags and use interpolations {{ }} to access the context",
700
+ type: "String",
701
+ fieldview: "textarea",
702
+ showIf: { wf_action_name: "Output" },
703
+ });
687
704
  actionConfigFields.push({
688
705
  label: "Table",
689
706
  name: "query_table",
@@ -959,7 +976,9 @@ router.get(
959
976
  },
960
977
  {
961
978
  headerTag: `<script type="module">mermaid.initialize({securityLevel: 'loose'${
962
- getState().getLightDarkMode(req.user) ? ",theme: 'dark'," : ""
979
+ getState().getLightDarkMode(req.user) === "dark"
980
+ ? ",theme: 'dark',"
981
+ : ""
963
982
  }});</script>`,
964
983
  },
965
984
  ],
@@ -1401,22 +1420,36 @@ router.post(
1401
1420
  trigger_id,
1402
1421
  configuration,
1403
1422
  };
1404
-
1405
- if (wf_step_id && wf_step_id !== "undefined") {
1406
- const wfStep = new WorkflowStep({ id: wf_step_id, ...step });
1407
-
1408
- await wfStep.update(step);
1409
- if (req.xhr) res.json({ success: "ok" });
1410
- else {
1411
- req.flash("success", req.__("Step saved"));
1412
- res.redirect(`/actions/configure/${step.trigger_id}`);
1423
+ try {
1424
+ if (wf_step_id && wf_step_id !== "undefined") {
1425
+ const wfStep = new WorkflowStep({ id: wf_step_id, ...step });
1426
+
1427
+ await wfStep.update(step);
1428
+ if (req.xhr) res.json({ success: "ok" });
1429
+ else {
1430
+ req.flash("success", req.__("Step saved"));
1431
+ res.redirect(`/actions/configure/${step.trigger_id}`);
1432
+ }
1433
+ } else {
1434
+ //insert
1435
+
1436
+ const id = await WorkflowStep.create(step);
1437
+ if (req.xhr)
1438
+ res.json({ success: "ok", set_fields: { wf_step_id: id } });
1439
+ else {
1440
+ req.flash("success", req.__("Step saved"));
1441
+ res.redirect(`/actions/configure/${step.trigger_id}`);
1442
+ }
1413
1443
  }
1414
- } else {
1415
- //insert
1416
- const id = await WorkflowStep.create(step);
1417
- if (req.xhr) res.json({ success: "ok", set_fields: { wf_step_id: id } });
1444
+ } catch (e) {
1445
+ const emsg =
1446
+ e.message ===
1447
+ 'duplicate key value violates unique constraint "workflow_steps_name_uniq"'
1448
+ ? `A step with the name ${wf_step_name} already exists`
1449
+ : e.message;
1450
+ if (req.xhr) res.json({ error: emsg });
1418
1451
  else {
1419
- req.flash("success", req.__("Step saved"));
1452
+ req.flash("error", emsg);
1420
1453
  res.redirect(`/actions/configure/${step.trigger_id}`);
1421
1454
  }
1422
1455
  }
@@ -1463,10 +1496,10 @@ router.get(
1463
1496
  case "Error":
1464
1497
  return run.error;
1465
1498
  case "Waiting":
1466
- if (run.wait_info?.form)
1499
+ if (run.wait_info?.form || run.wait_info.output)
1467
1500
  return a(
1468
1501
  { href: `/actions/fill-workflow-form/${run.id}` },
1469
- "Fill ",
1502
+ run.wait_info.output ? "Show " : "Fill ",
1470
1503
  run.current_step
1471
1504
  );
1472
1505
  return run.current_step;
@@ -1599,6 +1632,9 @@ router.get(
1599
1632
  run.status === "Waiting"
1600
1633
  ? tr(th("Waiting for"), td(JSON.stringify(run.wait_info)))
1601
1634
  : null,
1635
+ run.status === "Error"
1636
+ ? tr(th("Error message"), td(run.error))
1637
+ : null,
1602
1638
  tr(
1603
1639
  th("Context"),
1604
1640
  td(pre(text(JSON.stringify(run.context, null, 2))))
@@ -1663,7 +1699,8 @@ const getWorkflowStepUserForm = async (run, trigger, step, user) => {
1663
1699
 
1664
1700
  const form = new Form({
1665
1701
  action: `/actions/fill-workflow-form/${run.id}`,
1666
- blurb: step.configuration?.form_header || "",
1702
+ blurb: run.wait_info.output || step.configuration?.form_header || "",
1703
+ formStyle: run.wait_info.output ? "vert" : undefined,
1667
1704
  fields: (step.configuration.user_form_questions || []).map((q) => ({
1668
1705
  label: q.label,
1669
1706
  name: q.var_name,
@@ -1697,7 +1734,7 @@ router.get(
1697
1734
 
1698
1735
  const form = await getWorkflowStepUserForm(run, trigger, step, req.user);
1699
1736
  if (req.xhr) form.xhrSubmit = true;
1700
- const title = "Fill form";
1737
+ const title = run.wait_info.output ? "Workflow output" : "Fill form";
1701
1738
  res.sendWrap(title, renderForm(form, req.csrfToken()));
1702
1739
  })
1703
1740
  );
@@ -1791,9 +1828,14 @@ WORKFLOWS TODO
1791
1828
  delete is not always working?
1792
1829
  help file to explain steps, and context
1793
1830
 
1794
- workflow actions: ForLoop, EndForLoop, Output, ReadFile, WriteFile, APIResponse
1831
+ action explainer
1832
+ workflow actions: ForLoop, EndForLoop, ReadFile, WriteFile, APIResponse
1795
1833
 
1796
1834
  interactive workflows for not logged in
1835
+ correctly suggest new step name
1836
+ show end node in diagram
1837
+
1838
+ Error handlers
1797
1839
 
1798
1840
  show unconnected steps
1799
1841
  why is code not initialising
package/routes/tables.js CHANGED
@@ -56,7 +56,7 @@ const {
56
56
  } = require("@saltcorn/data/models/discovery");
57
57
  const { getState } = require("@saltcorn/data/db/state");
58
58
  const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
59
- const { tablesList, viewsList } = require("./common_lists");
59
+ const { tablesList, viewsList, getTriggerList } = require("./common_lists");
60
60
  const {
61
61
  InvalidConfiguration,
62
62
  removeAllWhiteSpace,
@@ -839,22 +839,6 @@ router.get(
839
839
  inbound_refs.map((tnm) => link(`/table/${tnm}`, tnm)).join(", ") +
840
840
  "<br>"
841
841
  : "",
842
- triggers.length
843
- ? req.__("Table triggers: ") +
844
- triggers
845
- .map((t) =>
846
- link(
847
- `/actions/configure/${
848
- t.id
849
- }?on_done_redirect=${encodeURIComponent(
850
- `table/${table.name}`
851
- )}`,
852
- t.name
853
- )
854
- )
855
- .join(", ") +
856
- "<br>"
857
- : "",
858
842
  !table.external &&
859
843
  !table.provider_name &&
860
844
  a(
@@ -866,7 +850,8 @@ router.get(
866
850
  ),
867
851
  ];
868
852
  }
869
- var viewCard;
853
+ let viewCard;
854
+ let triggerCard = "";
870
855
  if (fields.length > 0) {
871
856
  const views = await View.find(
872
857
  table.id ? { table_id: table.id } : { exttable_name: table.name }
@@ -899,6 +884,25 @@ router.get(
899
884
  req.__("Create view")
900
885
  ),
901
886
  };
887
+
888
+ triggerCard = {
889
+ type: "card",
890
+ id: "table-triggers",
891
+ title: req.__("Triggers on table"),
892
+ contents:
893
+ (triggers.length
894
+ ? await getTriggerList(triggers, req)
895
+ : p("Triggers run actions in response to events on this table")) +
896
+ a(
897
+ {
898
+ href: `/actions/new?table=${encodeURIComponent(
899
+ table.name
900
+ )}&on_done_redirect=${encodeURIComponent(`table/${table.name}`)}`,
901
+ class: "btn btn-primary",
902
+ },
903
+ req.__("Create trigger")
904
+ ),
905
+ };
902
906
  }
903
907
  const models = await Model.find({ table_id: table.id });
904
908
  const modelCard = div(
@@ -1093,6 +1097,7 @@ router.get(
1093
1097
  ]
1094
1098
  : []),
1095
1099
  ...(viewCard ? [viewCard] : []),
1100
+ ...(triggerCard ? [triggerCard] : []),
1096
1101
  {
1097
1102
  type: "card",
1098
1103
  title: req.__("Edit table properties"),
@@ -1177,6 +1182,7 @@ router.post(
1177
1182
  let notify = "";
1178
1183
  if (!rest.versioned) rest.versioned = false;
1179
1184
  if (!rest.has_sync_info) rest.has_sync_info = false;
1185
+ rest.is_user_group = !!rest.is_user_group;
1180
1186
  if (rest.ownership_field_id === "_formula") {
1181
1187
  rest.ownership_field_id = null;
1182
1188
  const fmlValidRes = expressionValidator(rest.ownership_formula);
@@ -1937,7 +1943,7 @@ router.post(
1937
1943
  const table = Table.findOne({ name });
1938
1944
 
1939
1945
  try {
1940
- await table.deleteRows({}, req.user);
1946
+ await table.deleteRows({}, req.user, true);
1941
1947
  req.flash("success", req.__("Deleted all rows"));
1942
1948
  } catch (e) {
1943
1949
  req.flash("error", e.message);