@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/auth/admin.js +20 -16
- package/auth/routes.js +12 -8
- package/locales/en.json +29 -1
- package/locales/si.json +1197 -0
- package/package.json +8 -8
- package/public/diagram_utils.js +21 -1
- package/public/saltcorn-common.js +40 -19
- package/public/saltcorn.js +11 -5
- package/routes/admin.js +2 -0
- package/routes/common_lists.js +34 -19
- package/routes/diagram.js +214 -199
- package/routes/fields.js +113 -6
- package/routes/index.js +2 -0
- package/routes/models.js +492 -0
- package/routes/page.js +10 -6
- package/routes/tables.js +67 -41
- package/routes/view.js +10 -6
- package/routes/viewedit.js +27 -5
- package/tests/view.test.js +217 -0
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 =
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
package/routes/viewedit.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
);
|
package/tests/view.test.js
CHANGED
|
@@ -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 });
|