@saltcorn/server 0.9.3-beta.7 → 0.9.3-rc.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/load_plugins.js CHANGED
@@ -46,6 +46,7 @@ const staticDependencies = {
46
46
  "@saltcorn/data/models/expression": require("@saltcorn/data/models/expression"),
47
47
  "@saltcorn/data/models/workflow": require("@saltcorn/data/models/workflow"),
48
48
  imapflow: require("imapflow"),
49
+ "node-fetch": require("node-fetch"),
49
50
  };
50
51
 
51
52
  /**
package/locales/en.json CHANGED
@@ -352,7 +352,6 @@
352
352
  "Next": "Next",
353
353
  "Save": "Save",
354
354
  "Calculated fields cannot have File type": "Calculated fields cannot have File type",
355
- "Calculated fields cannot have Key type": "Calculated fields cannot have Key type",
356
355
  "On Field": "On Field",
357
356
  "Field in %s table": "Field in %s table",
358
357
  "Action on row": "Action on row",
@@ -1337,5 +1336,11 @@
1337
1336
  "Destination page group": "Destination page group",
1338
1337
  "Your page groups": "Your page groups",
1339
1338
  "A group has pages with an eligible formula. When you request a group, then the first page where the formula matches gets served. This way, you can choose a page depending on the screen of the device.": "A group has pages with an eligible formula. When you request a group, then the first page where the formula matches gets served. This way, you can choose a page depending on the screen of the device.",
1340
- "Create page group": "Create page group"
1341
- }
1339
+ "Create page group": "Create page group",
1340
+ "Page groups": "Page groups",
1341
+ "Calculated non-stored fields cannot have Key type": "Calculated non-stored fields cannot have Key type",
1342
+ "Row click URL": "Row click URL",
1343
+ "Formula. Navigate to this URL when row is clicked": "Formula. Navigate to this URL when row is clicked",
1344
+ "Time of day": "Time of day",
1345
+ "UTC timezone": "UTC timezone"
1346
+ }
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.3-beta.7",
3
+ "version": "0.9.3-rc.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": "0.9.3-beta.7",
11
- "@saltcorn/builder": "0.9.3-beta.7",
12
- "@saltcorn/data": "0.9.3-beta.7",
13
- "@saltcorn/admin-models": "0.9.3-beta.7",
14
- "@saltcorn/filemanager": "0.9.3-beta.7",
15
- "@saltcorn/markup": "0.9.3-beta.7",
16
- "@saltcorn/sbadmin2": "0.9.3-beta.7",
10
+ "@saltcorn/base-plugin": "0.9.3-rc.1",
11
+ "@saltcorn/builder": "0.9.3-rc.1",
12
+ "@saltcorn/data": "0.9.3-rc.1",
13
+ "@saltcorn/admin-models": "0.9.3-rc.1",
14
+ "@saltcorn/filemanager": "0.9.3-rc.1",
15
+ "@saltcorn/markup": "0.9.3-rc.1",
16
+ "@saltcorn/sbadmin2": "0.9.3-rc.1",
17
17
  "@socket.io/cluster-adapter": "^0.2.1",
18
18
  "@socket.io/sticky": "^1.0.1",
19
19
  "adm-zip": "0.5.10",
@@ -304,6 +304,7 @@ var relationHelpers = (() => {
304
304
  const sourceTbl = this.tableNameCache[sourceTblName];
305
305
  if (!sourceTbl)
306
306
  throw new Error(`The table ${sourceTblName} does not exist`);
307
+ if (sourceTbl.id === subViewObj.table_id) result.push(`.${sourceTblName}`);
307
308
  const searcher = (current, path, level, visited) => {
308
309
  if (level > this.maxDepth) return;
309
310
  const visitedFkCopy = new Set(visited);
@@ -407,7 +407,7 @@ function get_form_record(e_in, select_labels) {
407
407
  const rec = {};
408
408
 
409
409
  const e = e_in.viewname
410
- ? $(`form[data-viewname=${e_in.viewname}]`)
410
+ ? $(`form[data-viewname="${e_in.viewname}"]`)
411
411
  : e_in.closest(".form-namespace");
412
412
 
413
413
  const form = $(e).closest("form");
@@ -494,3 +494,7 @@ table.help-md th:nth-child(2) {
494
494
  .progress-bar-radial {
495
495
  color: unset;
496
496
  }
497
+
498
+ tr[onclick] {
499
+ cursor: pointer;
500
+ }
package/routes/actions.js CHANGED
@@ -192,6 +192,13 @@ const triggerForm = async (req, trigger) => {
192
192
  "The table for which the trigger condition is checked."
193
193
  ),
194
194
  },
195
+ {
196
+ name: "channel",
197
+ label: req.__("Time of day"),
198
+ input_type: "time_of_day",
199
+ showIf: { when_trigger: "Daily" },
200
+ sublabel: req.__("UTC timezone"),
201
+ },
195
202
  {
196
203
  name: "channel",
197
204
  label: req.__("Channel"),
package/routes/fields.js CHANGED
@@ -74,8 +74,8 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
74
74
  validator: (vs) => {
75
75
  if (vs.calculated && vs.type === "File")
76
76
  return req.__("Calculated fields cannot have File type");
77
- if (vs.calculated && vs.type.startsWith("Key to"))
78
- return req.__("Calculated fields cannot have Key type");
77
+ if (vs.calculated && !vs.stored && vs.type.startsWith("Key to"))
78
+ return req.__("Calculated non-stored fields cannot have Key type");
79
79
  },
80
80
  fields: [
81
81
  new Field({
@@ -310,7 +310,12 @@ const fieldFlow = (req) =>
310
310
  const nrows = await table.countRows({});
311
311
  const existing_fields = table.getFields();
312
312
  const existingNames = existing_fields.map((f) => f.name);
313
- const fkey_opts = ["File", ...tables.map((t) => `Key to ${t.name}`)];
313
+ const fkey_opts = [
314
+ "File",
315
+ ...tables
316
+ .filter((t) => !t.provider_name && !t.external)
317
+ .map((t) => `Key to ${t.name}`),
318
+ ];
314
319
  const form = await fieldForm(
315
320
  req,
316
321
  fkey_opts,
@@ -1196,6 +1201,10 @@ router.post(
1196
1201
  formFields.forEach((ff) => {
1197
1202
  ff.class = ff.class ? `${ff.class} item-menu` : "item-menu";
1198
1203
  });
1204
+ if (req.query?.accept == "json") {
1205
+ res.json(formFields);
1206
+ return;
1207
+ }
1199
1208
 
1200
1209
  const form = new Form({
1201
1210
  formStyle: "vert",
@@ -18,6 +18,7 @@ const {
18
18
  error_catcher,
19
19
  addOnDoneRedirect,
20
20
  is_relative_url,
21
+ setTenant,
21
22
  } = require("./utils.js");
22
23
  const { setTableRefs, viewsList } = require("./common_lists");
23
24
  const Form = require("@saltcorn/data/models/form");
@@ -631,6 +632,7 @@ router.get(
631
632
  router.post(
632
633
  "/config/:name",
633
634
  isAdmin,
635
+ setTenant,
634
636
  error_catcher(async (req, res) => {
635
637
  const { name } = req.params;
636
638
 
@@ -769,6 +771,7 @@ router.post(
769
771
  router.post(
770
772
  "/saveconfig/:viewname",
771
773
  isAdmin,
774
+ setTenant,
772
775
  error_catcher(async (req, res) => {
773
776
  const { viewname } = req.params;
774
777
 
@@ -677,6 +677,197 @@ describe("many to many relations", () => {
677
677
  });
678
678
  });
679
679
 
680
+ describe("relation path to query and state", () => {
681
+ it("ChildList one layer", async () => {
682
+ const app = await getApp({ disableCsrf: true });
683
+ const loginCookie = await getAdminLoginCookie();
684
+ // my_department
685
+ await request(app)
686
+ .get(`/view/show_department_with_employee_list?id=1`)
687
+ .set("Cookie", loginCookie)
688
+ // view link
689
+ .expect(toInclude("/view/list_employees?department=1"))
690
+ // embedded list
691
+ .expect(toInclude("my_department"))
692
+ .expect(toNotInclude("department_without_employees"))
693
+ .expect(toInclude("manager"))
694
+ .expect(toInclude("my_employee"))
695
+ .expect(toInclude("/view/create_employee?department=1"));
696
+
697
+ // department_without_employees
698
+ await request(app)
699
+ .get(`/view/show_department_with_employee_list?id=2`)
700
+ .set("Cookie", loginCookie)
701
+ // view link
702
+ .expect(toInclude("/view/list_employees?department=2"))
703
+ // embedded list
704
+ .expect(toNotInclude("my_department"))
705
+ .expect(toNotInclude("department_without_employees"))
706
+ .expect(toNotInclude("manager"))
707
+ .expect(toNotInclude("my_employee"))
708
+ .expect(toNotInclude("/view/create_employee?department=1"));
709
+ });
710
+
711
+ it("ChildList two layers", async () => {
712
+ const app = await getApp({ disableCsrf: true });
713
+ const loginCookie = await getAdminLoginCookie();
714
+ await request(app)
715
+ .get(`/view/show_cover_with_artist_on_album?id=1`)
716
+ .set("Cookie", loginCookie)
717
+ // view link
718
+ .expect(
719
+ toInclude(
720
+ "/view/artist_plays_on_album_list?artist_plays_on_album.album.albums.cover=1"
721
+ )
722
+ )
723
+ // embedded list
724
+ .expect(toInclude("artist A"))
725
+ .expect(toInclude("artist B"))
726
+ .expect(toInclude("album A"));
727
+
728
+ await request(app)
729
+ .get(`/view/show_cover_with_artist_on_album?id=2`)
730
+ .set("Cookie", loginCookie)
731
+ // view link
732
+ .expect(
733
+ toInclude(
734
+ "/view/artist_plays_on_album_list?artist_plays_on_album.album.albums.cover=2"
735
+ )
736
+ )
737
+ // embedded list
738
+ .expect(toInclude("artist A"))
739
+ .expect(toNotInclude("artist B"))
740
+ .expect(toInclude("album B"));
741
+
742
+ await request(app)
743
+ .get(`/view/show_cover_with_artist_on_album?id=3`)
744
+ .set("Cookie", loginCookie)
745
+ // view link
746
+ .expect(
747
+ toInclude(
748
+ "/view/artist_plays_on_album_list?artist_plays_on_album.album.albums.cover=3"
749
+ )
750
+ )
751
+ // embedded list
752
+ .expect(toNotInclude("artist A"))
753
+ .expect(toNotInclude("artist B"))
754
+ .expect(toNotInclude("album A"))
755
+ .expect(toNotInclude("album B"));
756
+ });
757
+
758
+ it("OneToOneSHow", async () => {
759
+ const app = await getApp({ disableCsrf: true });
760
+ const loginCookie = await getAdminLoginCookie();
761
+ await request(app)
762
+ .get(`/view/show_cover_with_album?id=1`)
763
+ .set("Cookie", loginCookie)
764
+ // view link
765
+ .expect(toInclude("/view/show_album?cover=1"))
766
+ // embedded show
767
+ .expect(toInclude("album A"));
768
+
769
+ await request(app)
770
+ .get(`/view/show_cover_with_album?id=2`)
771
+ .set("Cookie", loginCookie)
772
+ // view link
773
+ .expect(toInclude("/view/show_album?cover=2"))
774
+ // embedded show
775
+ .expect(toInclude("blue cover"))
776
+ .expect(toInclude("album B"));
777
+
778
+ await request(app)
779
+ .get(`/view/show_cover_with_album?id=3`)
780
+ .set("Cookie", loginCookie)
781
+ // view link
782
+ .expect(toInclude("/view/show_album?cover=3"))
783
+ // embedded show
784
+ .expect(toInclude("red cover"))
785
+ .expect(toInclude("No row selected"));
786
+ });
787
+
788
+ it("Own", async () => {
789
+ const app = await getApp({ disableCsrf: true });
790
+ const loginCookie = await getAdminLoginCookie();
791
+ await request(app)
792
+ .get(`/view/show_artist_with_edit_artist?id=1`)
793
+ .set("Cookie", loginCookie)
794
+ // view link
795
+ .expect(toInclude("/view/edit_artist?id=1"))
796
+ // embedded edit
797
+ .expect(toInclude(`value="artist A"`));
798
+
799
+ await request(app)
800
+ .get(`/view/show_artist_with_edit_artist?id=2`)
801
+ .set("Cookie", loginCookie)
802
+ // view link
803
+ .expect(toInclude("/view/edit_artist?id=2"))
804
+ // embedded edit
805
+ .expect(toInclude(`value="artist B"`));
806
+ });
807
+
808
+ it("Parent", async () => {
809
+ const app = await getApp({ disableCsrf: true });
810
+ const loginCookie = await getAdminLoginCookie();
811
+ await request(app)
812
+ .get(`/view/show_album_with_cover?id=1`)
813
+ .set("Cookie", loginCookie)
814
+ // view link
815
+ .expect(toInclude("/view/show_cover?id=1"))
816
+ // embedded show
817
+ .expect(toInclude("green cover"));
818
+
819
+ await request(app)
820
+ .get(`/view/show_album_with_cover?id=2`)
821
+ .set("Cookie", loginCookie)
822
+ // view link
823
+ .expect(toInclude("/view/show_cover?id=2"))
824
+ // embedded show
825
+ .expect(toInclude("blue cover"));
826
+
827
+ await request(app)
828
+ .get(`/view/show_album_with_cover?id=3`)
829
+ .set("Cookie", loginCookie)
830
+ // view link
831
+ .expect(toNotInclude("/view/show_cover?id=3"))
832
+ // embedded show
833
+ .expect(toInclude("No row selected"));
834
+ });
835
+
836
+ it("RelationPath", async () => {
837
+ const app = await getApp({ disableCsrf: true });
838
+ const loginCookie = await getAdminLoginCookie();
839
+ await request(app)
840
+ .get(`/view/track_on_album_with_artists_on_album?id=1`)
841
+ .set("Cookie", loginCookie)
842
+ // view link
843
+ .expect(
844
+ toInclude(
845
+ "/view/artist_plays_on_album_list?.tracks_on_album.album.artist_plays_on_album$album=1"
846
+ )
847
+ )
848
+ // embedded show
849
+ .expect(toInclude("artist A"))
850
+ .expect(toInclude("artist B"))
851
+ .expect(toInclude("album A"))
852
+ .expect(toNotInclude("album B"));
853
+
854
+ await request(app)
855
+ .get(`/view/track_on_album_with_artists_on_album?id=2`)
856
+ .set("Cookie", loginCookie)
857
+ // view link
858
+ .expect(
859
+ toInclude(
860
+ "/view/artist_plays_on_album_list?.tracks_on_album.album.artist_plays_on_album$album=2"
861
+ )
862
+ )
863
+ // embedded show
864
+ .expect(toInclude("artist A"))
865
+ .expect(toNotInclude("artist B"))
866
+ .expect(toNotInclude("album A"))
867
+ .expect(toInclude("album B"));
868
+ });
869
+ });
870
+
680
871
  describe("legacy relations with relation path", () => {
681
872
  it("Independent feed", async () => {
682
873
  const app = await getApp({ disableCsrf: true });