Smart Actions
In legacy agents declaring a smart action was a two-step process:
First, you had to declare by changing the parameters of the
collectionfunction in the appropriatecollections/*.jsfile.Then, you had to implement the action by creating a route handler in the appropriate
routes/*.jsfile.
In the new agent, the process is simplified to a single step.
Code cheatsheet
type: 'single' type: 'bulk' type: 'global'
scope: 'Single' scope: 'Bulk' scope: 'Global'
download: true
generateFile: true
reference: 'otherCollection.id'
{ type: 'Collection', collectionName: 'otherCollection' }
enums: ['foo', 'bar']
{ type: 'Enum', enumValues: ['foo', 'bar'] }
RecordsGetter.getIdsFromRequest()
context.getRecordIds()
res.send(...)
return resultBuilder.success(...) return resultBuilder.error(...) ...
Steps
Step 1: Calling addAction for the appropriate collection
addAction for the appropriate collectionStart by calling the addAction function on the appropriate collection and passing the appropriate parameters.
Most notably, you will need to pass:
typeshould becomescopeNote that the values are now capitalized (e.g.
singlebecomesSingle)Legacy agents defaulted to
'bulk'if no type was specified. The new agent requires you to specify the scope.
downloadshould becomegenerateFile. This is still a boolean and the same value can be passed.endpointandhttpMethodshould be removed. The agent will now automatically handle the routing.
collection('companies', {
actions: [
{
name: 'Mark as Live',
type: 'bulk',
download: false,
endpoint: '/forest/actions/mark-as-live',
},
],
});agent.customizeCollection('companies', companies => {
companies.addAction('Mark as Live', {
scope: 'Bulk',
execute: async (context, resultBuilder) => {},
});
});Step 2: Porting the form definition
Forms are now defined in the form property of the action.
You can simply copy the field's definition from the legacy agent to the new agent with the following differences:
fieldsshould becomeform.widgetchoice is no longer supported. A default widget will be used depending on the field type.hookcan be removed, those will be handled by the new agent automatically.referenceno longer exists. Use{ type: 'Collection', collectionName: '...' }instead.enumsno longer exist. Use{ type: 'Enum', enumValues: ['...'] }instead.
collection('customers', {
actions: [
{
name: 'Charge credit card',
type: 'single',
fields: [
{
field: 'amount',
isRequired: true,
description: 'The amount (USD) to charge the credit card. Example: 42.50',
type: 'Number',
},
],
},
],
});agent.customizeCollection('customers', companies => {
companies.addAction('Charge credit card', {
// [...]
form: [
{
field: 'amount',
isRequired: true,
description: 'The amount (USD) to charge the credit card. Example: 42.50',
type: 'Number',
},
],
});
});Step 3: Porting the route to the new agent execute function
execute functionIn the legacy agent, users had to implement the action by creating a route handler in the appropriate routes/*.js file.
This is no longer needed as the new agent provides a context object that contains all the information that is needed to implement the action.
When porting the route handler to the new agent, you will need to:
Move the body of the route handler to the
executefunction of the action.Replace
RecordsGetter.getIdsFromRequest()call withcontext.getRecordIds().Replace
res.send(...)calls withreturn resultBuilder.success()orreturn resultBuilder.error(), or the appropriateresultBuildermethod.
router.post('/actions/mark-as-live', permissionMiddlewareCreator.smartAction(), (req, res) => {
const recordsGetter = new RecordsGetter(companies, request.user, request.query);
return recordsGetter
.getIdsFromRequest(req)
.then(companyIds => companies.update({ status: 'live' }, { where: { id: companyIds } }))
.then(() => res.send({ success: 'Company is now live!' }));
});agent.customizeCollection('companies', companies => {
companies.addAction('Mark as Live', {
// ...
execute: async (context, resultBuilder) => {
const companyIds = await context.getRecordIds();
await companies.update({ status: 'live' }, { where: { id: companyIds } });
return resultBuilder.success('Company is now live!');
},
});
});Step 4: Porting smart action hooks
Load hooks and change hooks have been replaced on the new agent by the possibility to use callbacks in the form definition.
Here is an example of a load hook where the default value of a field is set to 50 euros converted into dollars:
collection('customers', {
actions: [
{
name: 'Charge credit card',
type: 'single',
fields: [{ field: 'amount', type: 'Number' }],
// Here is the load hook that sets the default value of the amount field to
// 50 euros converted into dollars
hooks: {
load: async ({ fields, request }) => {
const amountField = fields.find(field => field.field === 'amount');
amountField.value = await convertEurosIntoDollars(50);
return fields;
},
},
},
],
});agent.customizeCollection('customers', companies => {
companies.addAction('Charge credit card', {
scope: 'Single',
form: [
{
field: 'amount',
type: 'Number',
// Set the default value of the amount field to 50 euros converted into dollars
// convertEurosIntoDollars is a function that returns a promise, it will be awaited
// automatically
defaultValues: () => convertEurosIntoDollars(50),
},
],
});
});And another for a change hook which makes a field required if the value of another field is greater than 100:
collection('customers', {
actions: [
{
name: 'Charge credit card',
type: 'single',
fields: [
{ field: 'amount', type: 'Number', hook: 'onAmountChange' },
{ field: 'motivation', type: 'String', isRequired: false },
],
// Change hook that makes the motivation field required if the amount is greater than 100
hooks: {
change: {
onAmountChange: async ({ fields, request }) => {
const amountField = fields.find(field => field.field === 'amount');
const motivationField = fields.find(field => field.field === 'motivation');
motivationField.isRequired = amountField.value > 100;
return fields;
},
},
},
},
],
});agent.customizeCollection('customers', companies => {
companies.addAction('Charge credit card', {
scope: 'Single',
form: [
{ field: 'amount', type: 'Number' },
{
field: 'motivation',
type: 'String',
isRequired: context => context.formValues.amount > 100,
},
],
});
});Last updated
Was this helpful?