/**
* Actions (Triggers) Handler
*
*/
const Router = require("express-promise-router");
const {
isAdmin,
setTenant,
error_catcher,
get_base_url,
} = require("./utils.js");
const {getState} = require("@saltcorn/data/db/state");
const Trigger = require("@saltcorn/data/models/trigger");
const router = new Router();
module.exports = router;
const {
mkTable,
renderForm,
link,
post_btn,
settingsDropdown,
post_dropdown_item,
post_delete_btn,
} = require("@saltcorn/markup");
const actions = require("@saltcorn/data/base-plugin/actions");
const Form = require("@saltcorn/data/models/form");
const {div, code, a, span} = require("@saltcorn/markup/tags");
const Table = require("@saltcorn/data/models/table");
const {getActionConfigFields} = require("@saltcorn/data/plugin-helper");
const {send_events_page} = require("../markup/admin.js");
const getActions = async () => {
return Object.entries(getState().actions).map(([k, v]) => {
const hasConfig = !!v.configFields;
return {
name: k,
hasConfig,
};
});
};
/**
* Actions (Trigger) List (GET)
*/
router.get(
"/",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const triggers = await Trigger.findAllWithTableName();
const actions = await getActions();
const base_url = get_base_url(req);
send_events_page({
res,
req,
active_sub: "Actions",
contents: {
above: [
{
type: "card",
title: req.__("Actions available"),
contents: div(
actions
.map((a) => span({class: "badge badge-primary"}, a.name))
.join(" ")
),
},
{
type: "card",
title: req.__("Triggers"),
contents: div(
mkTable(
[
{label: req.__("Name"), key: "name",},
{label: req.__("Action"), key: "action",},
{label: req.__("Table"), key: "table_name",},
{
label: req.__("When"),
key: (a) =>
a.when_trigger === "API call"
? `API: ${base_url}api/action/${a.name}`
: a.when_trigger,
},
{
label: req.__("Test run"),
key: (r) =>
r.table_id
? ""
: link(`/actions/testrun/${r.id}`, req.__("Test run")),
},
{
label: req.__("Edit"),
key: (r) =>
link(`/actions/trigger/${r.id}`, req.__("Edit")),
},
{
label: req.__("Configure"),
key: (r) =>
link(`/actions/configure/${r.id}`, req.__("Configure")),
},
{
label: req.__("Delete"),
key: (r) => post_delete_btn(`/actions/delete/${r.id}`, req),
},
],
triggers,
{hover: true}
),
link("/actions/trigger/new", req.__("Add trigger"))
),
},
],
},
});
})
);
/**
* Trigger Edit Form
* @param req
* @param trigger
* @returns {Promise<Form>}
*/
const triggerForm = async (req, trigger) => {
const actions = await getActions();
const tables = await Table.find({});
let id;
let form_action;
if(typeof trigger!=="undefined") {
id=trigger.id;
form_action = `/actions/trigger/${id}`;
}
else form_action = "/actions/trigger";
const form = new Form({
action: form_action,
fields: [
{
name: "name",
label: req.__("Name"),
type: "String",
sublabel: req.__("Name of action"),
},
{
name: "description",
label: req.__("Description"),
type: "String",
sublabel: req.__("Description allows you to give more information about the action"),
},
{
name: "action",
label: req.__("Action"),
input_type: "select",
required: true,
options: actions.map((t) => ({value: t.name, label: t.name})),
sublabel: req.__("The action to be taken when the trigger fires"),
},
{
name: "when_trigger",
label: req.__("When"),
input_type: "select",
required: true,
options: Trigger.when_options.map((t) => ({value: t, label: t})),
sublabel: req.__("Condition under which the trigger will fire"),
},
{
name: "table_id",
label: req.__("Table"),
input_type: "select",
options: [...tables.map((t) => ({value: t.id, label: t.name}))],
showIf: {when_trigger: ["Insert", "Update", "Delete"]},
sublabel: req.__("The table for which the trigger condition is checked."),
},
],
});
// if (trigger) {
// form.hidden("id");
// form.values = trigger;
// }
return form;
};
/**
* Create new Trigger (get)
*/
router.get(
"/trigger/new",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const form = await triggerForm(req);
send_events_page({
res,
req,
active_sub: "Actions",
sub2_page: "New",
contents: {
type: "card",
title: req.__("New trigger"),
contents: renderForm(form, req.csrfToken()),
},
});
})
);
/**
* Edit Trigger (get)
*/
router.get(
"/trigger/:id",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const {id} = req.params;
const trigger = await Trigger.findOne({id});
const form = await triggerForm(req, trigger);
form.values = trigger;
send_events_page({
res,
req,
active_sub: "Actions",
sub2_page: "Edit",
contents: {
type: "card",
title: req.__("Edit trigger %s", id),
contents: renderForm(form, req.csrfToken()),
},
});
})
);
/**
* POST for new trigger
*/
router.post(
"/trigger",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const form = await triggerForm(req);
form.validate(req.body);
if (form.hasErrors) {
send_events_page({
res,
req,
active_sub: "Actions",
sub2_page: "Edit",
contents: {
type: "card",
title: req.__("Edit trigger"),
contents: renderForm(form, req.csrfToken()),
},
});
} else {
let id;
if (form.values.id) {
id = form.values.id;
await Trigger.update(id, form.values);
} else {
const tr = await Trigger.create(form.values);
id = tr.id;
}
res.redirect(`/actions/configure/${id}`);
}
})
);
/**
* POST for existing trigger
*/
router.post(
"/trigger/:id",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const {id} = req.params;
const trigger = await Trigger.findOne({id});
// todo check that trigger exists
const form = await triggerForm(req, trigger);
form.validate(req.body);
if (form.hasErrors) {
send_events_page({
res,
req,
active_sub: "Actions",
sub2_page: "Edit",
contents: {
type: "card",
title: req.__("Edit trigger"),
contents: renderForm(form, req.csrfToken()),
},
});
} else {
await Trigger.update(trigger.id, form.values); //{configuration: form.values});
req.flash("success", "Action information saved");
res.redirect(`/actions/`);
}
})
);
/**
* Edit Trigger configuration (GET)
*
* /actions/configure/:id
*
* - get configuration fields
* - create form
*/
router.get(
"/configure/:id",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const {id} = req.params;
const trigger = await Trigger.findOne({id});
const action = getState().actions[trigger.action];
if (!action) {
req.flash("warning", req.__("Action not found"));
res.redirect(`/actions/`);
} else if (!action.configFields) {
req.flash("warning", req.__("Action not configurable"));
res.redirect(`/actions/`);
} else {
// get table related to trigger
const table = trigger.table_id
? await Table.findOne({id: trigger.table_id})
: null;
// get configuration fields
const cfgFields = await getActionConfigFields(action, table);
// create form
const form = new Form({
action: `/actions/configure/${id}`,
fields: cfgFields,
});
// populate form values
form.values = trigger.configuration;
// send events page
send_events_page({
res,
req,
active_sub: "Actions",
sub2_page: "Configure",
contents:
{
type: "card",
title: req.__("Configure trigger"),
contents: renderForm(form, req.csrfToken()),
},
});
}
})
);
/**
* Configure Trigger (POST)
*/
router.post(
"/configure/:id",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const {id} = req.params;
const trigger = await Trigger.findOne({id});
const action = getState().actions[trigger.action];
const table = trigger.table_id
? await Table.findOne({id: trigger.table_id})
: null;
const cfgFields = await getActionConfigFields(action, table);
const form = new Form({
action: `/actions/configure/${id}`,
fields: cfgFields,
});
form.validate(req.body);
if (form.hasErrors) {
send_events_page({
res,
req,
active_sub: "Actions",
sub2_page: "Configure",
contents: {
type: "card",
title: req.__("Configure trigger"),
contents: renderForm(form, req.csrfToken()),
},
});
} else {
await Trigger.update(trigger.id, {configuration: form.values});
req.flash("success", "Action configuration saved");
res.redirect(`/actions/`);
}
})
);
router.post(
"/delete/:id",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const {id} = req.params;
const trigger = await Trigger.findOne({id});
await trigger.delete();
res.redirect(`/actions/`);
})
);
router.get(
"/testrun/:id",
setTenant,
isAdmin,
error_catcher(async (req, res) => {
const {id} = req.params;
const trigger = await Trigger.findOne({id});
const output = [];
const fakeConsole = {
log(...s) {
output.push(div(code(s.join(" "))));
},
error(...s) {
output.push(
div(code({style: "color:red;font-weight:bold;"}, s.join(" ")))
);
},
};
let table, row;
if (trigger.table_id) {
table = await Table.findOne(trigger.table_id);
row = await table.getRow({});
}
try {
await trigger.runWithoutRow({
console: fakeConsole,
table,
row,
...(row || {}),
Table,
user: req.user,
});
} catch (e) {
fakeConsole.error(e.message);
}
if (output.length === 0) {
req.flash(
"success",
req.__(
"Action %s run successfully with no console output",
trigger.action
)
);
res.redirect(`/actions/`);
} else {
send_events_page({
res,
req,
active_sub: "Actions",
sub2_page: "Test run output",
contents: {
type: "card",
title: req.__("Test run output"),
contents: div(
div({class: "testrunoutput"}, output),
a(
{href: `/actions`, class: "mt-4 btn btn-primary"},
"« " + req.__("back to actions")
)
),
},
});
}
})
);