@saltcorn/server 0.8.7 → 0.8.8-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/routes/view.js CHANGED
@@ -8,6 +8,7 @@ const Router = require("express-promise-router");
8
8
 
9
9
  const View = require("@saltcorn/data/models/view");
10
10
  const Table = require("@saltcorn/data/models/table");
11
+ const Trigger = require("@saltcorn/data/models/trigger");
11
12
 
12
13
  const { text, style } = require("@saltcorn/markup/tags");
13
14
  const {
@@ -51,7 +52,7 @@ router.get(
51
52
  res.redirect("/");
52
53
  return;
53
54
  }
54
- const tic = state.logLevel >= 5 ? new Date() : null;
55
+ const tic = new Date();
55
56
 
56
57
  view.rewrite_query_from_slug(query, req.params);
57
58
  if (
@@ -85,11 +86,14 @@ router.get(
85
86
  res.set("SaltcornModalSaveIndicator", `true`);
86
87
  if (isModal && view.attributes?.popup_link_out)
87
88
  res.set("SaltcornModalLinkOut", `true`);
88
- if (tic) {
89
- const tock = new Date();
90
- const ms = tock.getTime() - tic.getTime();
91
- state.log(5, `View ${viewname} rendered in ${ms} ms`);
92
- }
89
+ const tock = new Date();
90
+ const ms = tock.getTime() - tic.getTime();
91
+ Trigger.emitEvent("PageLoad", null, req.user, {
92
+ text: req.__("View '%s' was loaded", viewname),
93
+ type: "view",
94
+ name: viewname,
95
+ render_time: ms,
96
+ });
93
97
  if (typeof contents === "object" && contents.goto)
94
98
  res.redirect(contents.goto);
95
99
  else
@@ -13,7 +13,12 @@ const { p, a, div, script, text, domReady, code, pre, tbody, tr, th, td } =
13
13
  tags;
14
14
 
15
15
  const { getState } = require("@saltcorn/data/db/state");
16
- const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
16
+ const {
17
+ isAdmin,
18
+ error_catcher,
19
+ addOnDoneRedirect,
20
+ is_relative_url,
21
+ } = require("./utils.js");
17
22
  const { setTableRefs, viewsList } = require("./common_lists");
18
23
  const Form = require("@saltcorn/data/models/form");
19
24
  const Field = require("@saltcorn/data/models/field");
@@ -675,7 +680,11 @@ router.post(
675
680
  view.name
676
681
  )
677
682
  );
678
- res.redirect(`/viewedit`);
683
+ let redirectTarget =
684
+ req.query.on_done_redirect && is_relative_url(req.query.on_done_redirect)
685
+ ? `/${req.query.on_done_redirect}`
686
+ : "/viewedit";
687
+ res.redirect(redirectTarget);
679
688
  })
680
689
  );
681
690
 
@@ -696,7 +705,11 @@ router.post(
696
705
  "success",
697
706
  req.__("View %s duplicated as %s", view.name, newview.name)
698
707
  );
699
- res.redirect(`/viewedit`);
708
+ let redirectTarget =
709
+ req.query.on_done_redirect && is_relative_url(req.query.on_done_redirect)
710
+ ? `/${req.query.on_done_redirect}`
711
+ : "/viewedit";
712
+ res.redirect(redirectTarget);
700
713
  })
701
714
  );
702
715
 
@@ -713,7 +726,11 @@ router.post(
713
726
  const { id } = req.params;
714
727
  await View.delete({ id });
715
728
  req.flash("success", req.__("View deleted"));
716
- res.redirect(`/viewedit`);
729
+ let redirectTarget =
730
+ req.query.on_done_redirect && is_relative_url(req.query.on_done_redirect)
731
+ ? `/${req.query.on_done_redirect}`
732
+ : "/viewedit";
733
+ res.redirect(redirectTarget);
717
734
  })
718
735
  );
719
736
 
@@ -805,7 +822,12 @@ router.post(
805
822
  : req.__(`Minimum role updated`);
806
823
  if (!req.xhr) {
807
824
  req.flash("success", message);
808
- res.redirect("/viewedit");
825
+ let redirectTarget =
826
+ req.query.on_done_redirect &&
827
+ is_relative_url(req.query.on_done_redirect)
828
+ ? `/${req.query.on_done_redirect}`
829
+ : "/viewedit";
830
+ res.redirect(redirectTarget);
809
831
  } else res.json({ okay: true, responseText: message });
810
832
  })
811
833
  );
@@ -8,11 +8,14 @@ const {
8
8
  toInclude,
9
9
  toNotInclude,
10
10
  resetToFixtures,
11
+ respondJsonWith,
11
12
  } = require("../auth/testhelp");
12
13
  const db = require("@saltcorn/data/db");
13
14
  const { getState } = require("@saltcorn/data/db/state");
14
15
  const View = require("@saltcorn/data/models/view");
15
16
  const Table = require("@saltcorn/data/models/table");
17
+ const Trigger = require("@saltcorn/data/models/trigger");
18
+ const Page = require("@saltcorn/data/models/page");
16
19
 
17
20
  const { plugin_with_routes } = require("@saltcorn/data/tests/mocks");
18
21
 
@@ -178,6 +181,220 @@ describe("render view with slug", () => {
178
181
  });
179
182
  });
180
183
 
184
+ describe("action row_variable", () => {
185
+ const createFilterView = async ({
186
+ configuration,
187
+ rowVariable,
188
+ rndid,
189
+ actionName,
190
+ viewName,
191
+ rowLimit,
192
+ }) => {
193
+ const table = Table.findOne({ name: "books" });
194
+ const filterCfg = {
195
+ layout: {
196
+ type: "action",
197
+ configuration: configuration,
198
+ action_name: actionName,
199
+ action_row_variable: rowVariable,
200
+ action_style: "btn-primary",
201
+ minRole: 10,
202
+ rndid: rndid,
203
+ },
204
+ columns: [
205
+ {
206
+ type: "Action",
207
+ action_name: actionName,
208
+ action_row_variable: rowVariable,
209
+ action_style: "btn-primary",
210
+ minRole: 100,
211
+ configuration: configuration,
212
+ rndid: rndid,
213
+ },
214
+ ],
215
+ };
216
+ if (rowLimit) {
217
+ filterCfg.layout.action_row_limit = rowLimit;
218
+ filterCfg.columns[0].action_row_limit = rowLimit;
219
+ }
220
+ await View.create({
221
+ table_id: table.id,
222
+ name: viewName,
223
+ viewtemplate: "Filter",
224
+ configuration: filterCfg,
225
+ min_role: 100,
226
+ });
227
+ };
228
+
229
+ it("run_code_none_row_var", async () => {
230
+ const app = await getApp({ disableCsrf: true });
231
+ const loginCookie = await getAdminLoginCookie();
232
+ await createFilterView({
233
+ configuration: {
234
+ run_where: "Client page",
235
+ code: 'console.log("hello");',
236
+ },
237
+ rowVariable: "none",
238
+ rndid: "q6b06q",
239
+ actionName: "run_js_code",
240
+ viewName: "run_code_none_row_var",
241
+ });
242
+ await request(app)
243
+ .post("/view/run_code_none_row_var/run_action")
244
+ .set("Cookie", loginCookie)
245
+ .send({
246
+ rndid: "q6b06q",
247
+ })
248
+ .set("Content-Type", "application/json")
249
+ .set("Accept", "application/json")
250
+ .expect(
251
+ respondJsonWith(200, (resp) => {
252
+ return (
253
+ resp.success === "ok" && resp.eval_js === 'console.log("hello");'
254
+ );
255
+ })
256
+ );
257
+ });
258
+ it("insert_row_from_state", async () => {
259
+ const app = await getApp({ disableCsrf: true });
260
+ const loginCookie = await getAdminLoginCookie();
261
+ await createFilterView({
262
+ configuration: {
263
+ table: "books",
264
+ row_expr: `{
265
+ author: row.author,
266
+ pages: row.pages,
267
+ }`,
268
+ },
269
+ rowVariable: "state",
270
+ rndid: "u6b06u",
271
+ actionName: "insert_any_row",
272
+ viewName: "insert_row_from_state",
273
+ });
274
+ await request(app)
275
+ .post(
276
+ "/view/insert_row_from_state/run_action?author=author_from_state&pages=234"
277
+ )
278
+ .set("Cookie", loginCookie)
279
+ .send({
280
+ rndid: "u6b06u",
281
+ })
282
+ .set("Content-Type", "application/json")
283
+ .set("Accept", "application/json")
284
+ .expect(respondJsonWith(200, (resp) => resp.success === "ok"));
285
+ const books = Table.findOne({ name: "books" });
286
+ const actual = await books.getRows({ author: "author_from_state" });
287
+ expect(actual.length).toBe(1);
288
+ });
289
+ it("run_code_eatch_matching", async () => {
290
+ const app = await getApp({ disableCsrf: true });
291
+ const loginCookie = await getAdminLoginCookie();
292
+ await createFilterView({
293
+ configuration: {
294
+ run_where: "Client page",
295
+ code: 'console.log("hello");',
296
+ },
297
+ rowVariable: "each_matching_row",
298
+ rndid: "b6b06b",
299
+ actionName: "run_js_code",
300
+ viewName: "run_code_eatch_matching",
301
+ });
302
+ const testHelper = async (query, resultCount) => {
303
+ await request(app)
304
+ .post(`/view/run_code_eatch_matching/run_action?${query}`)
305
+ .set("Cookie", loginCookie)
306
+ .send({ rndid: "b6b06b" })
307
+ .set("Content-Type", "application/json")
308
+ .set("Accept", "application/json")
309
+ .expect(
310
+ respondJsonWith(200, (resp) => {
311
+ if (resp.success !== "ok") return false;
312
+ return (
313
+ (resultCount === 0 && !resp.eval_js) ||
314
+ (Array.isArray(resp.eval_js) &&
315
+ resp.eval_js.length === resultCount)
316
+ );
317
+ })
318
+ );
319
+ };
320
+ await testHelper("author=le", 2);
321
+ await testHelper("author=Herman", 1);
322
+ await testHelper("author=Christian", 0);
323
+ });
324
+
325
+ it("insert_row_each_matching", async () => {
326
+ const app = await getApp({ disableCsrf: true });
327
+ const loginCookie = await getAdminLoginCookie();
328
+ await createFilterView({
329
+ configuration: {
330
+ table: "books",
331
+ row_expr: `{
332
+ author: row.author + "_copy",
333
+ pages: row.pages,
334
+ }`,
335
+ },
336
+ rowVariable: "each_matching_row",
337
+ rndid: "a6b06a",
338
+ actionName: "insert_any_row",
339
+ viewName: "insert_row_each_matching",
340
+ });
341
+ const testHelper = async (query) => {
342
+ await request(app)
343
+ .post(`/view/insert_row_each_matching/run_action?${query}`)
344
+ .set("Cookie", loginCookie)
345
+ .send({ rndid: "a6b06a" })
346
+ .set("Content-Type", "application/json")
347
+ .set("Accept", "application/json")
348
+ .expect(
349
+ respondJsonWith(200, (resp) => {
350
+ return resp.success === "ok";
351
+ })
352
+ );
353
+ };
354
+ const books = Table.findOne({ name: "books" });
355
+ let oldLength = (await books.getRows()).length;
356
+ await testHelper("author=le");
357
+ let newLength = (await books.getRows()).length;
358
+ expect(newLength).toBe(oldLength + 2);
359
+ oldLength = newLength;
360
+ await testHelper("author=_copy");
361
+ newLength = (await books.getRows()).length;
362
+ expect(newLength).toBe(oldLength + 2);
363
+ oldLength = newLength;
364
+ await testHelper("author=Christian");
365
+ newLength = (await books.getRows()).length;
366
+ expect(newLength).toBe(oldLength);
367
+ });
368
+
369
+ it("each_matching_row: action_row_limit", async () => {
370
+ const app = await getApp({ disableCsrf: true });
371
+ const loginCookie = await getAdminLoginCookie();
372
+ await createFilterView({
373
+ configuration: {
374
+ run_where: "Client page",
375
+ code: 'console.log("hello");',
376
+ },
377
+ rowVariable: "each_matching_row",
378
+ rndid: "c6b06c",
379
+ actionName: "run_js_code",
380
+ viewName: "author_filter_row_limit",
381
+ rowLimit: 2,
382
+ });
383
+ await request(app)
384
+ .post("/view/author_filter_row_limit/run_action?author=le")
385
+ .set("Cookie", loginCookie)
386
+ .send({ rndid: "c6b06c" })
387
+ .set("Content-Type", "application/json")
388
+ .set("Accept", "application/json")
389
+ .expect(
390
+ respondJsonWith(200, (resp) => {
391
+ if (resp.success !== "ok") return false;
392
+ return Array.isArray(resp.eval_js) && resp.eval_js.length === 2;
393
+ })
394
+ );
395
+ });
396
+ });
397
+
181
398
  describe("inbound relations", () => {
182
399
  it("view with inbound relation", async () => {
183
400
  const app = await getApp({ disableCsrf: true });