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 10 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
5 changes: 3 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1153,8 +1153,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 @@ -1229,6 +1229,20 @@
"label": "Custom data (optional)",
"help": "These properties will be added to the event and will overwrite existing values"
}
},
"jsonEditor": {
"title": "Json Editor",
joristirado marked this conversation as resolved.
Show resolved Hide resolved
"popupTitle": "Add/Edit json action",
joristirado marked this conversation as resolved.
Show resolved Hide resolved
"name": "Name",
"placeholder": {
"name": "Action Name"
},
"editor": {
"label": "Json data",
joristirado marked this conversation as resolved.
Show resolved Hide resolved
"errorMessages": {
"jsonEditor": "Invalid Content"
}
}
}
},
"oldCallflows": {
Expand Down
254 changes: 253 additions & 1 deletion submodules/misc/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ define(function(require) {
post: 'POST',
put: 'PUT'
}
},
jsonEditor: {
unsupportedCallflowsList: {},
callflowsListSchema: {}
}
}
},
Expand Down Expand Up @@ -1271,6 +1275,48 @@ define(function(require) {
edit: function(node, callback) {
self.miscRenderEditWebhook(node, callback);
}
},
'json_editor[]': {
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.misc.jsonEditor.unsupportedCallflowsList)) {
self.miscSchemasList(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();
unsupportedModules = _.difference(getCallflowModules, supportedModules);

self.appFlags.misc.jsonEditor.unsupportedCallflowsList = unsupportedModules;
self.miscRenderEditJson(node, callback);
});
} else {
self.miscRenderEditJson(node, callback);
}
}

}
});
},
Expand Down Expand Up @@ -1552,6 +1598,143 @@ define(function(require) {
});
},

miscRenderEditJson: 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.misc.jsonEditor.unsupportedCallflowsList
},
submodule: 'misc'
})),
$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)) {
$template
.find('#save')
.removeClass('disabled');
} else {
//disable save button when there are errors
joristirado marked this conversation as resolved.
Show resolved Hide resolved
$template
.find('#save')
.addClass('disabled');
}
}
},
jsoneditor = monster.ui.jsoneditor($target, options, node.data.data);

jsoneditor.set(node.data.data, {});
self.miscSetSchema($template, jsoneditor, callback);

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

//do nothing when button is disabled
joristirado marked this conversation as resolved.
Show resolved Hide resolved
if ($(this).hasClass('disabled')) {
return;
}

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

node.caption = selectedOption;
node.module = selectedOption;

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

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

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

self.miscSetSchema($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();
}
}
});
},

miscSetSchema: function(template, jsoneditor, callback) {
var self = this,
$template = template,
selectedOption = $template.find('#name').val(),
callflowSchema = self.appFlags.misc.jsonEditor.callflowsListSchema[selectedOption];

if (callflowSchema) {
jsoneditor.setSchema(callflowSchema);
} else {
//disable save button and select options until schema is set for validation
Copy link
Contributor

Choose a reason for hiding this comment

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

How about abstracting this behavior into a local function, taking as parameter $template? That function can then be named after its behavior, negating the need of describing what it does as a comment (as its name would indicate that).

$template
.find('#save')
.addClass('disabled');

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

//get schema from API if it's not stored locally and set it in jsoneditor
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be better as part of the description of app#miscGetSchem, not at the call site. The reason is, what if this methods gets called somewhere else?

self.miscGetSchema({
data: {
schemaId: 'callflows.' + selectedOption
},
success: function(data) {
//validate if schema has references to othe subschemas
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, the name of the method already describe its purpose here, not sure what this comment tries to clarify? Any more in-depth explaining should be moved to the method's description.

var refList = self.miscValidateSubSchema(data)

/*if refList is true, fetch subschemas and set them in jsoneditor,
otherwise set schema directly*/
refList ? self.miscGetSubSchema(refList,data,jsoneditor) : jsoneditor.setSchema(data);

//set schema in local storage
self.appFlags.misc.jsonEditor.callflowsListSchema[selectedOption] = data;

//enable save button and select options after schema is set
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, this behavior can be abstracted into a function if not straighforward enough that it needs a comment.

$template
.find('#save')
.removeClass('disabled');

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

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

/* API helpers */
miscDeviceList: function(callback) {
var self = this;
Expand Down Expand Up @@ -1619,7 +1802,76 @@ define(function(require) {
callback && callback(data.data);
}
});
}
},

miscSchemasList: function(callback) {
var self = this;

self.callApi({
resource: 'schemas.list',
success: function(data, status) {
callback && callback(data.data);
}
});
},

miscGetSchema: 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);
}
});
},

/*@param reflist: array of the references to other schemas
*@param parentSchema: schema with references to other schemas
*@param jsoneditor: function to set schema in jsoneditor whith its references
fetch all subschemas of partenSchema so they can be set on the jsoneditor*/
pcandia marked this conversation as resolved.
Show resolved Hide resolved
miscGetSubSchema: function(refList,parentSchema,jsoneditor) {
var self = this;

monster.series(refList.map(ref =>
pcandia marked this conversation as resolved.
Show resolved Hide resolved
function(callback) {
self.miscGetSchema({
data: {
schemaId: ref
},
success: function(data) {
/*passing array with id,data pair to return on result callback*/
callback(null,[data.id,data]);
}
})
},
), function(err,results) {
/*convert results array of arrays to object
and setting it on the jsoneditor alongside its
parent schema*/
jsoneditor.setSchema(parentSchema,Object.fromEntries(results));
})
},

/*@param schema: a json schema to verify if it has references
validates according to the path where references can be found
if no references foun return false*/
miscValidateSubSchema: function(schema) {
if(_.has(schema, "properties.config.$ref")) {
return [schema.properties.config["$ref]"]]
} else if(_.has(schema, "properties.macros.items.oneOf")) {
return schema.properties.macros.items.oneOf.map(item => item["$ref"]);
} else {
return false
}
}
};

return app;
Expand Down
26 changes: 26 additions & 0 deletions submodules/misc/views/json_editor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="dialog_popup callflows-port">
<form id="form_json_editor" method="post" action="#">
<div class="form_content">
<div class="popup_field">
<label for="name" class="control-label">
{{i18n.callflows.jsonEditor.name}}
</label>
<select id="name" name="name">
{{#select name}}
{{#each unsupportedCallflowsList}}
<option value="{{ this }}">{{ this }}</option>
{{/each}}
{{/select}}
</select>
<div class="form_content">
<div class="popup_field">
<div id="jsoneditor" style="width: 400px; height: 400px;"></div>
</div>
</div>
</div>
</div>
</form>
<div class="buttons-center">
<button id="save" class="monster-button monster-button-primary">{{ i18n.save }}</button>
</div>
</div>