Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MACF-7: Add generic action so that custom flows can be seen and edited #142

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
30394e4
MACF-32: Add a generic action so that custom flows can be seen and ed…
pcandia Oct 14, 2020
7f890bf
MACF-33: Replace input field with select from a list of callflow modu…
pcandia Oct 19, 2020
f4ae0ce
MACF-34: Validate against the selected callflow action schema before …
pcandia Oct 21, 2020
4bb0d18
Filter out 'callflows' option'
pcandia Jan 19, 2021
3c8ed90
Merge branch 'master' into MACF-7
pcandia Dec 10, 2021
3b7cb37
fixing missing refs:
Ramos95 Jan 7, 2022
2aa0415
Revert "fixing missing refs:"
Ramos95 Feb 24, 2022
b12eed9
refactor code:
Ramos95 Feb 24, 2022
e88e32c
fixing some minor spacing issues
Ramos95 Feb 24, 2022
41f91ae
Merge branch 'master' into MACF-7
pcandia Mar 15, 2022
411b364
fixing eslint spacing errors
Ramos95 Mar 16, 2022
bc5d704
Fix linter
pcandia Mar 23, 2022
085748e
fixing naming typos
Ramos95 Apr 29, 2022
9d2997a
refator code: implementing review changes
Ramos95 Apr 29, 2022
02b109c
organizing all the code related to json_editor to its own submodule
Ramos95 Sep 14, 2022
fa6943c
removing all the code related to json_editor since now its used as a …
Ramos95 Sep 14, 2022
b13542c
registering the json_editor submodule on appSubmodules array list
Ramos95 Sep 14, 2022
bbb9af9
Merge branch 'master' into MACF-7
pcandia Sep 22, 2022
70e074e
removing arrow function and using lodash map function instead
Ramos95 Sep 22, 2022
f54cec9
simplifying schema storage:
Ramos95 Sep 22, 2022
3b97d35
removing console.log lines
Ramos95 Sep 22, 2022
d125a08
refactoring code: storing fetched schemas on cache
Ramos95 Oct 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ define(function(require) {
'faxbox',
'featurecodes',
'groups',
'jsoneditor',
'media',
'menu',
'misc',
Expand Down Expand Up @@ -1153,8 +1154,9 @@ define(function(require) {
action = self.actions[actionName] || {};

this.id = -1;
this.actionName = actionName;
this.module = action.module;
//set actionName to json_editor[] and module to their current module for non supported actions
pcandia marked this conversation as resolved.
Show resolved Hide resolved
this.actionName = _.isEmpty(action) ? 'json_editor[]' : actionName;
this.module = action.module ? action.module : actionName.replace(/\[(.*?)\]/g, '');
this.key = '_';
this.parent = null;
this.children = [];
Expand Down
14 changes: 14 additions & 0 deletions i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,20 @@
"label": "Custom data (optional)",
"help": "These properties will be added to the event and will overwrite existing values"
}
},
"jsonEditor": {
"title": "JSON Editor",
"popupTitle": "Add/Edit JSON action",
"name": "Name",
"placeholder": {
"name": "Action Name"
},
"editor": {
"label": "JSON data",
"errorMessages": {
"jsonEditor": "Invalid Content"
}
}
}
},
"oldCallflows": {
Expand Down
2 changes: 1 addition & 1 deletion submodules/blacklist/blacklist.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ define(function(require) {
}
},

/* blacklistBindEvents: function(data, template, callbacks) {
/*blacklistBindEvents: function(data, template, callbacks) {
var self = this,
addNumber = function(e) {
var number = template.find('#number_value').val();
Expand Down
2 changes: 1 addition & 1 deletion submodules/groups/groups.js
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,7 @@ define(function(require) {

$('#strategy', popup_html).bind('change', function() {
var strategy = $(this).val(),
$delay = $('.options .option.delay', popup_html);
$delay = $('.options .option.delay', popup_html),
$delayTitle = $('.options .delay_title', popup_html);

if (strategy === 'single') {
Expand Down
331 changes: 331 additions & 0 deletions submodules/jsoneditor/jsoneditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
define(function(require) {
var $ = require('jquery'),
_ = require('lodash'),
monster = require('monster');

return {
subscribe: {
'callflows.fetchActions': 'jsonEditorDefineAction'
},

appFlags: {
unsupportedCallflowsList: {},
callflowsListSchema: {}
/* cachedSchemas: {} */
},

jsonEditorDefineAction: function(args) {
var self = this,
nodes = args.actions;

$.extend(nodes, {
'json_editor[]': _.merge({
name: self.i18n.active().callflows.jsonEditor.title,
icon: 'pencil', //graph2
category: self.i18n.active().oldCallflows.advanced_cat,
module: 'jsonEditor',
tip: self.i18n.active().callflows.jsonEditor.tip,
data: {},
rules: [],
isUsable: 'true',
weight: 170,
caption: function(node) {
return node.module !== 'jsonEditor' ? node.module : '';
},
edit: function(node, callback) {
if (_.isEmpty(self.appFlags.unsupportedCallflowsList)) {
self.jsonEditorGetSchemasList(function(data) {
var supportedModules = _
.chain(monster.apps.callflows.actions)
.map('module')
.uniq()
.filter(_.isString)
.value(),
getCallflowModules = _
.chain(data)
.filter(function(module) {
return module.startsWith('callflows.');
})
.map(function(module) {
return module.replace('callflows.', '');
})
.value();
var unsupportedModules = _.difference(getCallflowModules, supportedModules);

self.appFlags.unsupportedCallflowsList = unsupportedModules;
self.jsonEditorRender(node, callback);
});
} else {
self.jsonEditorRender(node, callback);
}
}
})
});
},

/**
* render the json editor as a pop up element
* @param {object} node - view to render the element
* @param {function} callback - callback to run on successful schema fetch
*/
jsonEditorRender: function(node, callback) {
var self = this,
popup,
initTemplate = function() {
var $template = $(self.getTemplate({
name: 'json_editor',
data: {
name: node.caption ? node.caption : '',
unsupportedCallflowsList: self.appFlags.unsupportedCallflowsList
},
submodule: 'jsoneditor'
})),
$target = $template.find('#jsoneditor'),
options = {
mode: 'code',
modes: ['code', 'text'],
onValidate: function(json) {
var errors = [];

if (_.isEmpty(json)) {
errors.push({
path: ['empty'],
message: 'Required json properties are missing.'
});
}

return errors;
},
onValidationError: function(errors) {
if (_.isEmpty(errors)) {
self.jsonEditorToggleSaveButton($template, true);
} else {
self.jsonEditorToggleSaveButton($template, false);
}
}
},
jsoneditor = monster.ui.jsoneditor($target, options, node.data.data);
jsoneditor.set(node.data.data, {});
self.jsonEditorInitSchema($template, jsoneditor, callback);

$template.find('#save').on('click', function(e) {
e.preventDefault();

if ($(this).hasClass('disabled')) {
return;
}

var selectedSchema = $template.find('#name').val();
var content = jsoneditor.get();

node.caption = selectedSchema;
node.module = selectedSchema;

_.each(content, function(value, key) {
node.setMetadata(key, value);
});

popup.dialog('close');
});

$template.find('#name').on('change', function(e) {
e.preventDefault();

self.jsonEditorInitSchema($template, jsoneditor, callback);
});

return $template;
};

popup = monster.ui.dialog(initTemplate(), {
title: self.i18n.active().callflows.jsonEditor.popupTitle,
width: 500,
beforeClose: function() {
if (_.isFunction(callback)) {
callback();
}
}
});
},

jsonEditorInitSchema: function(template, jsoneditor, callback) {
var self = this,
$template = template,
selectedSchema = $template.find('#name').val(),
schemaId = 'callflows.' + selectedSchema,
callflowSchema = self.appFlags.callflowsListSchema[schemaId];

if (callflowSchema) {
var subSchemas = _.pick(self.appFlags.callflowsListSchema, self.jsonEditorValidateSubSchema(callflowSchema));
jsoneditor.setSchema(callflowSchema, subSchemas);
} else {
self.jsonEditorToggleSaveButton($template, false);

$template
.find('#name')
.prop('disabled', true);

self.jsonEditorGetSchema({
data: {
schemaId: schemaId
},
success: function(data) {
var refList = self.jsonEditorGetRefList(data);

self.jsonEditorSetSchema(refList, data, jsoneditor);

self.jsonEditorToggleSaveButton($template, true);

$template
.find('#name')
.prop('disabled', false);

callback(null, data);
}
});
}
},

/**
* wrapper function to return a list of schema refs that has not been fetched yet
* @param {object} schema
* @returns filtered array of unfetched refs
*/
jsonEditorGetRefList: function(schema) {
var self = this;
var refList = self.jsonEditorValidateSubSchema(schema);
return self.jsonEditorFilterCachedSubSchemaRef(refList);
},

/**
* filter the schemas that have not been already fetched to save API calls
* @param {array} refs list of subschemas refs to fetch
* @returns {array} list of filtered ref that have not been already fetched
*/
jsonEditorFilterCachedSubSchemaRef: function(refs) {
var self = this;
return _.filter(refs,
function(ref) {
return !self.appFlags.callflowsListSchema[ref];
});
},

/**
* Verify if json schema has references
* @param {object} schema
*/
jsonEditorValidateSubSchema: function(schema) {
if (_.has(schema, 'properties.config.$ref')) {
return [schema.properties.config['$ref]']];
} else if (_.has(schema, 'properties.macros.items.oneOf')) {
return _.map(schema.properties.macros.items.oneOf, '$ref');
} else {
return [];
}
},

/**
* get the list of available schemas
* @param {function} next - callback to run on success
*/
jsonEditorGetSchemasList: function(next) {
var self = this;

self.callApi({
resource: 'schemas.list',
success: _.flow(
_.partial(_.get, _, 'data'),
next
)
});
},
/**
* @param {object} args
* @param args.data - object with id property to define the schema th fetch
* @param args.success - function to use as the CallApi success property
*/
jsonEditorGetSchema: function(args) {
var self = this,
data = args.data;

self.callApi({
resource: 'schemas.get',
data: {
schemaId: data.schemaId
},
success: function(data, status) {
args.hasOwnProperty('success') && args.success(data.data);
},
error: function(parsedError) {
args.hasOwnProperty('error') && args.error(parsedError);
}
});
},

/**
* toggle editor save button enabled/disabled
* @param {object} template - JSON editor template
* @param {boolean} enable - true to enable save button, false to disable
*/
jsonEditorToggleSaveButton: function(template, enabled) {
if (enabled) {
template.find('#save').removeClass('disabled');
} else {
template.find('#save').addClass('disabled');
}
},

/**
* validate if the JSON schema should be set with sub schemas or not
* @param {string[]} refList - list of references
* @param {object} data - JSON schema
* @param {object} jsoneditor - JSON editor instance
*/
jsonEditorSetSchema: function(refList, data, jsoneditor) {
var self = this;
if (refList) {
self.jsonEditorGetSubSchema(
refList,
data,
jsoneditor,
_.pick(self.appFlags.callflowsListSchema, refList));
} else {
jsoneditor.setSchema(data);
self.appFlags.callflowsListSchema = _.merge(self.appFlags.callflowsListSchema, { [data.id]: data });
}
},

/**
* Get the required sub schemas from the main JSON schema
* @param {string[]} refList - list of subschemas to fetch
* @param {object} parentSchema - schema to which subschemas are added
* @param {object} jsoneditor - jsoneditor instance
* @param {object} cachedSchemas - schemas already fetched
*/
jsonEditorGetSubSchema: function(refList, parentSchema, jsoneditor, cachedSchemas) {
var self = this;

monster.parallel(
_.chain(refList)
.keyBy()
.mapValues(function(ref) {
//api call to get subschema
return function(callback) {
self.jsonEditorGetSchema({
data: {
schemaId: ref
},
success: function(data) {
callback(null, data);
}
});
};
Comment on lines +312 to +322
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since nested schemas could reference ones that were already pulled and saved locally, we could check our cache here first, to avoid performing unnecessary API requests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that schemas are not stored separated it can check if it is already stored and avpid calling api again

})
.value()
, function(err, results) {
self.appFlags.callflowsListSchema = _.merge(self.appFlags.callflowsListSchema, { [parentSchema.id]: parentSchema }, results);
jsoneditor.setSchema(parentSchema, _.merge(cachedSchemas, results));
});
}
};
});
Loading