@saltcorn/server 1.1.1-beta.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/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.1-beta.0",
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.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",
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/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");
@@ -495,7 +495,7 @@ router.post(
495
495
  function genWorkflowDiagram(steps) {
496
496
  const stepNames = steps.map((s) => s.name);
497
497
  const nodeLines = steps.map(
498
- (s) => ` ${s.name}["\`**${s.name}**
498
+ (s) => ` ${s.mmname}["\`**${s.name}**
499
499
  ${s.action_name}\`"]:::wfstep${s.id}`
500
500
  );
501
501
 
@@ -503,36 +503,39 @@ function genWorkflowDiagram(steps) {
503
503
  const linkLines = [];
504
504
  let step_ix = 0;
505
505
  for (const step of steps) {
506
- if (step.initial_step) linkLines.push(` _Start --> ${step.name}`);
507
- if (step.action_name === "ForLoop") {
508
- linkLines.push(
509
- ` ${step.name} --> ${step.configuration.for_loop_step_name}`
510
- );
511
- } else if (stepNames.includes(step.next_step)) {
506
+ if (step.initial_step) linkLines.push(` _Start --> ${step.mmname}`);
507
+ if (stepNames.includes(step.next_step)) {
512
508
  linkLines.push(
513
- ` ${step.name}-- <i class="fas fa-plus add-btw-nodes btw-nodes-${step.id}-${step.next_step}"></i> ---${step.next_step}`
509
+ ` ${step.mmname}-- <i class="fas fa-plus add-btw-nodes btw-nodes-${step.id}-${step.next_step}"></i> ---${step.mmnext}`
514
510
  );
515
511
  } else if (step.next_step) {
516
512
  let found = false;
517
513
  for (const otherStep of stepNames)
518
514
  if (step.next_step.includes(otherStep)) {
519
- linkLines.push(` ${step.name} --> ${otherStep}`);
515
+ linkLines.push(
516
+ ` ${step.mmname} --> ${WorkflowStep.mmescape(otherStep)}`
517
+ );
520
518
  found = true;
521
519
  }
522
520
  if (!found) {
523
521
  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}`
522
+ ` ${step.mmname}-- <a href="/actions/stepedit/${step.trigger_id}/${step.id}">Error: missing next step in ${step.mmname}</a> ---_End_${step.mmname}`
525
523
  );
526
524
  nodeLines.push(
527
- ` _End_${step.name}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
525
+ ` _End_${step.mmname}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
528
526
  );
529
527
  }
530
528
  } else if (!step.next_step) {
531
- linkLines.push(` ${step.name} --> _End_${step.name}`);
529
+ linkLines.push(` ${step.mmname} --> _End_${step.mmname}`);
532
530
  nodeLines.push(
533
- ` _End_${step.name}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
531
+ ` _End_${step.mmname}:::wfadd${step.id}@{ shape: circle, label: "<i class='fas fa-plus with-link'></i>" }`
534
532
  );
535
533
  }
534
+ if (step.action_name === "ForLoop") {
535
+ linkLines.push(
536
+ ` ${step.mmname}-.->${WorkflowStep.mmescape(step.configuration.loop_body_initial_step)}`
537
+ );
538
+ }
536
539
  if (step.action_name === "EndForLoop") {
537
540
  // TODO this is not correct. improve.
538
541
  let forStep;
@@ -542,7 +545,7 @@ function genWorkflowDiagram(steps) {
542
545
  break;
543
546
  }
544
547
  }
545
- if (forStep) linkLines.push(` ${step.name} --> ${forStep.name}`);
548
+ if (forStep) linkLines.push(` ${step.mmname} --> ${forStep.mmname}`);
546
549
  }
547
550
  step_ix += 1;
548
551
  }
@@ -581,7 +584,29 @@ const getWorkflowConfig = async (req, id, table, trigger) => {
581
584
  ],
582
585
  });
583
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
+ }
584
608
  return (
609
+ copilot_form +
585
610
  pre({ class: "mermaid" }, genWorkflowDiagram(steps)) +
586
611
  script(
587
612
  { defer: "defer" },
@@ -641,16 +666,6 @@ window.addEventListener('DOMContentLoaded',tryAddWFNodes)`
641
666
  );
642
667
  };
643
668
 
644
- const jsIdentifierValidator = (s) => {
645
- if (!s) return "An identifier is required";
646
- if (s.includes(" ")) return "Spaces not allowd";
647
- let badc = "'#:/\\@()[]{}\"!%^&*-+*~<>,.?|"
648
- .split("")
649
- .find((c) => s.includes(c));
650
-
651
- if (badc) return `Character ${badc} not allowed`;
652
- };
653
-
654
669
  const getWorkflowStepForm = async (
655
670
  trigger,
656
671
  req,
@@ -700,19 +715,12 @@ const getWorkflowStepForm = async (
700
715
  }
701
716
  } catch {}
702
717
  }
718
+ const builtInActionExplainers = WorkflowStep.builtInActionExplainers();
703
719
  const actionsNotRequiringRow = Trigger.action_options({
704
720
  notRequireRow: true,
705
721
  noMultiStep: true,
706
722
  builtInLabel: "Workflow Actions",
707
- builtIns: [
708
- "SetContext",
709
- "TableQuery",
710
- "Output",
711
- "DataOutput",
712
- "WaitUntil",
713
- "WaitNextTick",
714
- "UserForm",
715
- ],
723
+ builtIns: Object.keys(builtInActionExplainers),
716
724
  forWorkflow: true,
717
725
  });
718
726
  const triggers = Trigger.find({
@@ -721,152 +729,8 @@ const getWorkflowStepForm = async (
721
729
  triggers.forEach((tr) => {
722
730
  if (tr.description) actionExplainers[tr.name] = tr.description;
723
731
  });
724
- actionExplainers.SetContext = "Set variables in the context";
725
- actionExplainers.TableQuery = "Query a table into a variable in the context";
726
- actionExplainers.Output =
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.";
730
- actionExplainers.WaitUntil = "Pause until a time in the future";
731
- actionExplainers.WaitNextTick =
732
- "Pause until the next scheduler invocation (at most 5 minutes)";
733
- actionExplainers.UserForm =
734
- "Ask a user one or more questions, pause until they are answered";
735
-
736
- actionConfigFields.push({
737
- label: "Form header",
738
- sublabel: "Text shown to the user at the top of the form",
739
- name: "form_header",
740
- type: "String",
741
- showIf: { wf_action_name: "UserForm" },
742
- });
743
- actionConfigFields.push({
744
- label: "User ID",
745
- name: "user_id_expression",
746
- type: "String",
747
- sublabel: "Optional. If blank assigned to user starting the workflow",
748
- showIf: { wf_action_name: "UserForm" },
749
- });
750
- actionConfigFields.push({
751
- label: "Resume at",
752
- name: "resume_at",
753
- sublabel:
754
- "JavaScript expression for the time to resume. <code>moment</code> is in scope.",
755
- type: "String",
756
- showIf: { wf_action_name: "WaitUntil" },
757
- });
758
- actionConfigFields.push({
759
- label: "Context values",
760
- name: "ctx_values",
761
- sublabel:
762
- "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",
763
- type: "String",
764
- fieldview: "textarea",
765
- class: "validate-expression",
766
- default: "{}",
767
- showIf: { wf_action_name: "SetContext" },
768
- });
769
- actionConfigFields.push({
770
- label: "Output text",
771
- name: "output_text",
772
- sublabel:
773
- "Message shown to the user. Can contain HTML tags and use interpolations {{ }} to access the context",
774
- type: "String",
775
- fieldview: "textarea",
776
- showIf: { wf_action_name: "Output" },
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
- });
795
- actionConfigFields.push({
796
- label: "Table",
797
- name: "query_table",
798
- type: "String",
799
- required: true,
800
- attributes: { options: (await Table.find()).map((t) => t.name) },
801
- showIf: { wf_action_name: "TableQuery" },
802
- });
803
- actionConfigFields.push({
804
- label: "Query",
805
- name: "query_object",
806
- sublabel: "Where object, example <code>{manager: 1}</code>",
807
- type: "String",
808
- required: true,
809
- class: "validate-expression",
810
- default: "{}",
811
- showIf: { wf_action_name: "TableQuery" },
812
- });
813
- actionConfigFields.push({
814
- label: "Variable",
815
- name: "query_variable",
816
- sublabel: "Context variable to write to query results to",
817
- type: "String",
818
- required: true,
819
- validator: jsIdentifierValidator,
820
- showIf: { wf_action_name: "TableQuery" },
821
- });
822
- actionConfigFields.push(
823
- new FieldRepeat({
824
- name: "user_form_questions",
825
- showIf: { wf_action_name: "UserForm" },
826
- fields: [
827
- {
828
- label: "Label",
829
- name: "label",
830
- type: "String",
831
- sublabel:
832
- "The text that will shown to the user above the input elements",
833
- },
834
- {
835
- label: "Variable name",
836
- name: "var_name",
837
- type: "String",
838
- sublabel:
839
- "The answer will be set in the context with this variable name",
840
- validator: jsIdentifierValidator,
841
- },
842
- {
843
- label: "Input Type",
844
- name: "qtype",
845
- type: "String",
846
- required: true,
847
- attributes: {
848
- options: [
849
- "Yes/No",
850
- "Checkbox",
851
- "Free text",
852
- "Multiple choice",
853
- //"Multiple checks",
854
- "Integer",
855
- "Float",
856
- //"File upload",
857
- ],
858
- },
859
- },
860
- {
861
- label: "Options",
862
- name: "options",
863
- type: "String",
864
- sublabel: "Comma separated list of multiple choice options",
865
- showIf: { qtype: ["Multiple choice", "Multiple checks"] },
866
- },
867
- ],
868
- })
869
- );
732
+ Object.assign(actionExplainers, builtInActionExplainers);
733
+ actionConfigFields.push(...(await WorkflowStep.builtInActionConfigFields()));
870
734
 
871
735
  const form = new Form({
872
736
  action: addOnDoneRedirect(`/actions/stepedit/${trigger.id}`, req),
@@ -1561,7 +1425,7 @@ router.post(
1561
1425
  res.redirect(`/actions/configure/${step.trigger_id}`);
1562
1426
  }
1563
1427
  }
1564
- if (_after_step) {
1428
+ if (_after_step && _after_step !== "undefined") {
1565
1429
  const astep = await WorkflowStep.findOne({
1566
1430
  id: _after_step,
1567
1431
  trigger_id,
@@ -1583,6 +1447,28 @@ router.post(
1583
1447
  })
1584
1448
  );
1585
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
+
1586
1472
  router.post(
1587
1473
  "/delete-step/:step_id",
1588
1474
  isAdmin,
package/routes/utils.js CHANGED
@@ -107,6 +107,7 @@ const setLanguage = (req, res, state) => {
107
107
  } else if (req.cookies?.lang) {
108
108
  req.setLocale(req.cookies?.lang);
109
109
  }
110
+ if (req.user) Object.freeze(req.user);
110
111
  set_custom_http_headers(res, req, state);
111
112
  };
112
113
 
@@ -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,