Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
{ "label": "Create Tweet" }/* Create "Designer". */
var designer = appmixer.ui.Designer({
el: '#your-designer',
options: {
menu: [
{ label: 'Rename', event: 'flow:rename' },
{ label: 'Insights Logs', event: 'flow:insights-logs' },
{ label: 'Clone', event: 'flow:clone' },
{ label: 'Share', event: 'flow:share' },
{ label: 'Export to SVG', event: 'flow:export-svg' },
{ label: 'Export to PNG', event: 'flow:export-png' },
{ label: 'Print', event: 'flow:print' }
],
// buttons in the toolbar
toolbar: [
['undo', 'redo'],
['zoom-to-fit', 'zoom-in', 'zoom-out'],
['logs']
]
}
});props: {
value: { type: null, required: false },
variables: { type: Object, default: () => ({}) },
inputManifest: { type: Object, default: () => ({}) },
descriptorPath: { type: String, default: '' },
errors: { type: Array, default: () => ([]) },
disabled: { type: Boolean, default: false }
}props: {
context: { type: Object },
value: { type: null, required: false },
errors: { type: Array, default: () => ([]) },
disabled: { type: Boolean, default: false },
}computed: {
config() {
return this.inputManifest.config;
},
},computed: {
config() {
return this.context.inputManifest.config;
},
},{ "name": "appmixer.twitter.statuses.CreateTweet" }{
"author": "David Durman <[email protected]>"
}{
"name": "appmixer.twilio.sms.SendSMS",
"version": "1.0.0",
"private": true,
"main": "SendSMS.js",
"author": "David Durman <[email protected]>",
"dependencies": {
"twilio": "^2.11.0"
}
}{
"authConfig": {
"service": "appmixer:deepai"
}
}{
"icon": "data:image/svg+xml;base64,PD94bWwgdmV..."
}{
"marker": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL..."
}{
"auth": {
"service": "appmixer:google",
"scope": [
"https://mail.google.com/",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send"
]
}
}{
"outPorts": [
{
"name": "weather",
"options": [
{ "label": "Temperature", "value": "main.temp" },
{ "label": "Pressure", "value": "main.pressure" },
{ "label": "Humidity", "value": "main.humidity" },
{ "label": "Sunrise time (unix, UTC)", "value": "sys.sunrise" },
{ "label": "Sunset time (unix, UTC)", "value": "sys.sunset" },
{ "label": "City name", "value": "name" },
{ "label": "Weather description", "value": "weather[0].description" },
{ "label": "Weather icon code", "value": "weather[0].icon" },
{ "label": "Weather icon URL", "value": "weather[0].iconUrl" }
]
}
]
}The appmixer.ui.Components is a UI widget that displays all available applications and components.


curl -XPOST "http://localhost:2200/user/auth" -d '{"username": "[email protected]", "password": "abc321"}' -H 'Content-Type: application/json'{"user":{"id":"5c88c7cc04a917256c726c3d","username":"[email protected]","isActive":false,"email":"[email protected]","plan":"free"},"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkzMTA0LCJleHAiOjE1NTUwODUxMDR9.sdU3Jt2MjmVuBNak0FwR0ETcjleRyiA6bqjMPA6f-ak"}curl "http://localhost:2200/flows/count" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkyNjA5LCJleHAiOjE1NTUwODQ2MDl9.9jVcqY0qo9Q_1GeK9Fg14v7OrdpWvzmqnv4jDMZfqnI"var myCustomApi = {
/* the key must match an existing API method */
myCustomApiMethod(/* arguments of the original method */) {
return new Promise((resolve) => {
resolve(myCustomResponse);
});
}
}
/* Use a custom API on the entire SDK instance */
var appmixer = new Appmixer({ api: myCustomApi });
/* Use a custom API on a particular SDK UI widget */
var designer = new appmixer.ui.Designer({ api: myCustomApi });{"count":5}var components = appmixer.ui.Components({
el: '#your-accounts-div'
});
components.open();User-Agent property. This is set by default to Appmixer, but it can be overwritten using Backoffice:{
"description": "This action gets the current weather conditions for a location."
}
{
"firePatterns": ['*', 1]
}{
"firePatterns": [1, 1]
}{
"firePatterns": [
['*', 1],
[1, 0]
]
}{
"76a77abf-d5ec-4b1c-b6e8-031359a6a640": {
"type": "appmixer.utils.timers.Timer",
"label": "Timer",
"x": 265,
"y": 115,
"config": {
"properties": {
"interval": 15
}
}
},
"a0828f32-34b8-4c8d-b6b3-1d82ca305921": {
"type": "appmixer.utils.weather.GetCurrentWeather",
"label": "GetCurrentWeather",
"source": {
"location": {
"76a77abf-d5ec-4b1c-b6e8-031359a6a640": [
"out"
]
}
},
"x": 515,
"y": 115,
"config": {
"transform": {
"location": {
"76a77abf-d5ec-4b1c-b6e8-031359a6a640": {
"out": {
"type": "json2new",
"lambda": {
"city": "Prague",
"units": "metric"
}
}
}
}
}
}
},
"11f02d1e-106e-4cf5-aae2-5514e531ea4d": {
"type": "appmixer.slack.list.SendChannelMessage",
"label": "SendChannelMessage",
"source": {
"message": {
"a0828f32-34b8-4c8d-b6b3-1d82ca305921": [
"weather"
]
}
},
"x": 735,
"y": 115,
"config": {
"properties": {
"channelId": "C3FNGP5K5"
},
"transform": {
"message": {
"a0828f32-34b8-4c8d-b6b3-1d82ca305921": {
"weather": {
"type": "json2new",
"lambda": {
"text": "City: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[name]}}}\nHumidity: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[main.humidity]}}}\nPressure: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[main.pressure]}}}\nTemperature: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[main.temp]}}}"
}
}
}
}
}
}
}
}



{
"name": "appmixer.twilio.sms.SendSMS",
"author": "David Durman <[email protected]>",
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUwMCIgaGVp...",
"description": "Send SMS text message through Twilio.",
"private": false,
"auth": {
"service": "appmixer:twilio"
},
"outPorts": [
{
"name": "sent",
"options": [
{ "label": "Message Sid", "value": "sid" }
]
}
],
"inPorts": [
{
"name": "message",
"schema": {
"type": "object",
"properties": {
"body": { "type": "string" },
"to": { "type": "string" },
"from": { "type": "string" }
},
"required": [
"from", "to"
]
},
"inspector": {
"inputs": {
"body": {
"type": "text",
"label": "Text message",
"tooltip": "Text message that should be sent.",
"index": 1
},
"from": {
"type": "select",
"label": "From number",
"placeholder": "Type number",
"tooltip": "Select Twilio phone number.",
"index": 2,
"source": {
"url": "/component/appmixer/twilio/sms/ListFromNumbers?outPort=numbers",
"data": {
"transform": "./transformers#fromNumbersToSelectArray"
}
}
},
"to": {
"type": "text",
"label": "To number",
"tooltip": "The destination phone number. <br/><br/>Format with a '+' and country code e.g., +16175551212 (E.164 format).",
"index": 3
}
}
}
}
],
"localization": {
"cs": {
"label": "Pošli SMS",
"description": "Pošli SMS pomocí Twilia",
"inPorts[0].name": "Zpráva",
"inPorts[0].inspector.inputs.body.label": "Textová zpráva",
"inPorts[0].inspector.inputs.from.label": "Číslo volajícího",
"inPorts[0].inspector.inputs.from.placeholder": "Hledej číslo",
"outPorts[0].name": "Odesláno",
"outPorts[0].options[sid].label": "Sid zprávy"
},
"sk": {
"label": "Pošli SMS",
"description": "Pošli SMS pomocou Twilia",
"inPorts[0].name": "Správa",
"inPorts[0].inspector.inputs.body.label": "Textová správa",
"inPorts[0].inspector.inputs.from.label": "číslo volajúceho",
"outPorts[0].name": "Odoslané",
"outPorts[0].options[sid].label": "Sid správy"
}
}
}{
"name": "appmixer.slack.list.NewChannelMessageRT",
"description": "As opposed to NewChannelMessage and NewPrivateChannelMessage this trigger fires immediately every time a message is posted to a channel. Works both for public and private channels.",
"webhook": true,
"auth": {
"service": "appmixer:slack", // appmixer:slack will be used as a 'key'
"scope": [
"channels:read",
"channels:history"
]
},
"outPorts": [
...
}module.exports = {
async tick(context) {
context.auth.clientId;
context.auth.clientSecret;
context.auth.orAnythingElse;
// all of those will be available at context.config as well
context.config.clientId;
context.config.clientSecret;
context.config.orAnythingElse;
}
}Appmixer SDK allows you to change all the strings of all the UI widgets it provides (Designer, FlowManager, Insights, ...). This is especially useful to localize the entire Appmixer UI.
// component.json file without "auth" section
{
// the property "name" will be used to look for configuration stored through
// the Backoffice. Appmixer will try to look for a key [vendor]:[service] and
// [vendor]:[service]:[module]
"name": "appmixer.utils.tasks.RequestApprovalEmail",
"description": "People Task is a feature of Appmixer that allows human interaction inside workflows.",
"webhook": true,
"inPorts": [
{
...
}var appmixer = new Appmixer({ baseUrl: BASE_URL });
appmixer.set('theme', THEME);appmixer.set('theme', {
$flowManager: {
backgroundColor: 'lightgray',
color: 'blue',
$header: {
$btnCreateFlow: {
'@hover': {
backgroundColor: 'black',
color: 'white'
}
}
},
$thumbnails: {
spacing: '15px',
$flow: {
color: 'white'
}
}
}
});var accounts = appmixer.ui.Accounts({
el: '#your-accounts-div'
});
accounts.on('flow:open', function(flowId) {
designer.set('flowId', flowId);
designer.open();
});
accounts.open();docker cp google52658022a92d779c.html appmixer-401_engine_1:/usr/src/appmixer/gridd/publicvar appmixer = new Appmixer({ baseUrl: BASE_URL });
appmixer.set('strings', STRINGS);$ cd appmixer/frontend/appmixer/
$ open demo.html# Download the Appmixer SDK:
$ wget http://localhost:8080/appmixer/appmixer.js
# Download the demo page:
$ wget http://localhost:8080/appmixer/demo.html
# Download the example theme object:
$ wget http://localhost:8080/appmixer/theme.js
$ open demo.html{
"name": "appmixer.utils.controls.OnStart",
"author": "Martin Krčmář <[email protected]>",
"label": "On Flow Start",
"description": "This trigger fires once and only once the flow starts.",
"icon": "data:image/svg+xml;base64,PD94bWwgdmV...",
"outPorts": [
{
"name": "out",
"schema": {
"properties": {
"started": {
"type": "string",
"format": "date-time"
}
},
"required": [ "started" ]
},
"options": [
{ "label": "Start time", "value": "started" }
]
}
]
}curl "https://api.appmixer.com/apps" -H "Authorization: Bearer [ACCESS_TOKEN]"{
"inPorts": [
{
"name": "message",
"schema": {
"type": "object",
"properties": {
"body": { "type": "string" },
"phoneNumber": { "type": "string" }
},
"required": [ "phoneNumber" ]
},
"inspector": {
"inputs": {
"body": {
"type": "text",
"group": "transformation",
"label": "Text message",
"index": 1
},
"phoneNumber": {
"type": "text",
"group": "transformation",
"label": "Phone number",
"index": 2
}
},
"groups": {
"transformation": {
"label": "Transformation",
"index": 1
}
}
}
}
]
}










GET /service-config[
{
"serviceId": "appmixer:google",
"clientID": "my-global-client-id",
"clientSecret": "my-global-client-secret"
},
{
"serviceId": "appmixer:evernote",
"sandbox": true,
}
]{
"length": 146737,
"chunkSize": 261120,
"uploadDate": "2020-07-24T19:19:49.755Z",
"filename": "chart1.png",
"md5": "1d0ed5eb2cacbab4526de272837102dd",
"metadata": {
"userId": "5f15d59cef81ecb3344fab55"
},
"contentType": "image/png",
"fileId": "f179c163-2ad8-4f9d-bce5-95200691b7f9"
}{
"user": {
"id": "5c88c7cc04a917256c726c3d",
"username":"[email protected]",
"isActive": false,
"email": "[email protected]",
"plan":"free"
},
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkyNjA5LCJleHAiOjE1NTUwODQ2MDl9.9jVcqY0qo9Q_1GeK9Fg14v7OrdpWvzmqnv4jDMZfqnI"
}{
"serviceId": "appmixer:google",
"clientID": "my-global-client-id",
"clientSecret": "my-global-client-secret"
}{
"serviceId": "appmixer:google",
"clientID": "my-global-client-id",
"clientSecret": "my-global-client-secret"
}{
"serviceId": "appmixer:google",
"clientID": "my-global-client-id",
"clientSecret": "my-global-client-secret"
}{}module.exports = {
rules: [
{
limit: 2000,
throttling: 'window-sliding',
window: 1000 * 60 * 60 * 24,
scope: 'userId',
resource: 'messages.send'
},
{
limit: 3,
window: 1000,
throttling: 'window-sliding',
queueing: 'fifo',
resource: 'messages.send',
scope: 'userId'
}
]
};const moment = require('moment');
module.exports = {
rules: [{
// The limit is 25 per day per one user.
limit: 25,
window: 1000 * 60 * 60 * 24,
throttling: 'window-sliding',
queueing: 'fifo',
resource: 'shares',
scope: 'userId'
}, {
// The limit is 125000 per day per application.
limit: 125000,
throttling: {
type: 'window-fixed',
getStartOfNextWindow: () => {
// Daily quotas refresh at midnight PST.
return moment.utc().startOf('day').add(1, 'day').add(8, 'hours').valueOf();
}
},
resource: 'shares'
}]
};{
"length": 146737,
"chunkSize": 261120,
"uploadDate": "2020-07-24T20:23:38.565Z",
"filename": "chart1.png",
"md5": "1d0ed5eb2cacbab4526de272837102dd",
"metadata": {
"userId": "5f15d59cef81ecb3344fab55"
},
"contentType": "image/png",
"fileId": "8cb8fed0-0cd8-4478-8372-9f7cb4eb16e3"
}{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkyNjA5LCJleHAiOjE1NTUwODQ2MDl9.9jVcqY0qo9Q_1GeK9Fg14v7OrdpWvzmqnv4jDMZfqnI"
}{
"id": "58593f07c3ee4f239dc69ff7",
"username": "[email protected]",
"isActive": true,
"email": "[email protected]",
"scope": [
"user"
],
"plan": "beta"
}$ curl -XPOST "https://api.appmixer.com/flows" -H "Content-Type: application/json" -d '{ "flow": FLOW_DESCRIPTOR, "name": "My Flow #1", "customFields": { "category": "healthcare" } }'$ curl -XPUT "https://api.appmixer.com/flows/9089f275-f5a5-4796-ba23-365412c5666e" -H "Content-Type: application/json" -d '{ "customFields": { "category": "healthcare" } }'appmixer.api.createFlow('myFlow', FLOW_DESCRIPTOR, {
"customFields": { "category": "healthcare" }
});appmixer.api.updateFlow('9089f275-f5a5-4796-ba23-365412c5666e', {
"customFields": { "category": "healthcare" }
});$ curl "https://api.appmixer.com/flows?filter=customFields.category:healthcare" -H "Authorization: Bearer [ACCESS_TOKEN]"$ curl "https://api.appmixer.com/flows?filter=customFields.category:!healthcare" -H "Authorization: Bearer [ACCESS_TOKEN]"appmixer.api.getFlows({
limit: 20,
offset: 0,
projection: "-thumbnail",
sort: "mtime:-1",
filter: "customFields.category:healthcare"
})const flowManagerTemplates = new appmixer.ui.FlowManager({
el: '#your-flow-manager-templates-div',
options: {
customFilter: {
'customFields.template': 'true'
}
}
});
flowManagerTemplates.open();const flowManagerFlows = new appmixer.ui.FlowManager({
el: '#your-flow-manager-flows-div',
options: {
customFilter: {
'customFields.template': '!true'
}
}
});
flowManagerFlows.open();Routes for setting ACL. Used in Backoffice.
This tutorial shows how flows can be shared in Appmixer either using the API or the SDK.




var insightsChartEditor = appmixer.ui.InsightsChartEditor({
el: '#your-insights-chart-editor-div'
});
appmixer.api.getCharts().then(function(charts) {
var myChart = charts[0]; // Assuming we have at least one chart.
insightsChartEditor.set('chartId', myChart.chartId);
insightsChartEditor.open();
});var integrations = appmixer.ui.Integrations({
el: '#your-integrations-div',
});
integrations.on('integration:create', async function(templateId) {
// Create integration flow as a clone of the template. Disconnect
// accounts because they might not be shared with the end user.
var integrationId = await appmixer.api.cloneFlow(templateId, { connectAccounts: false });
// Identify the clone as an integration.
await appmixer.api.updateFlow(integrationId, { templateId });
// Open appmixer.ui.Wizard
var wizard = appmixer.ui.Wizard({
el: '#your-wizard-div',
flowId: integrationId,
});
wizard.open();
integrations.reload();
});
integrations.on('integration:edit', function(integrationId) {
// Open appmixer.ui.Wizard
var wizard = appmixer.ui.Wizard({
el: '#your-wizard-div',
flowId: integrationId,
});
wizard.open();
});
integrations.open();appmixer.set('strings', {
flowManager: {
header: {
btnCreate: 'Create new Flow',
search: 'Search flows'
}
}
});appmixer.set('strings', {
time: {
months: [...],
monthsShort: [...],
weekDaysShort: [...],
ordinal(number){ ... },
relativeTime: {...}
}
});ordinal(number) {
const b = number % 10;
const output = (~~(number % 100 / 10) === 1) ? 'th'
: (b === 1) ? 'st'
: (b === 2) ? 'nd'
: (b === 3) ? 'rd' : 'th';
return number + output;
}appmixer.set('strings', {
flowManager: {
header: {
pagination: 'of {{total}} flow|of {{total}} flows'
}
}
});appmixer.set('strings', {
flowManager: {
messageNoFlows: {
text: 'Click (@createFlow)here(/@createFlow) to create a new flow'
}
}
}); <script>
var BASE_URL = '<your-base-url>';
var USERNAME = '<your-username>';
var PASSWORD = '<your-password>';appmixer.api.authenticateUser(USERNAME, PASSWORD).then((auth) => {
appmixer.set('accessToken', auth.token);
start();
});<div id="your-flow-manager"></div>var flowManager = appmixer.ui.FlowManager({ el: '#your-flow-manager' });flowManager.open();flowManager.on('flow:open', (flowId) => {
designer.set('flowId', flowId);
flowManager.close();
designer.open();
});
appmixer.set('accessToken', null);var HtmlWebpackPlugin = require('html-webpack-plugin');
var AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
}),
new AddAssetHtmlPlugin({
filepath: require.resolve('path/to/appmixer/appmixer.js'),
}),
],
};var insightsDashboard = appmixer.ui.InsightsDashboard({
el: '#your-insights-dashboard-div'
});
var insightsChartEditor = appmixer.ui.InsightsChartEditor({
el: '#your-insights-chart-editor-div'
});
insightsDashboard.on('chart:open', function(chartId) {
insightsChartEditor.open();
insightsChartEditor.set('chartId', chartId);
});
insightsDashboard.on('chart:remove', function(chartId) {
appmixer.api.deleteChart(chartId).then(function() {
insightsDashboard.reload();
});
});
insightsDashboard.open();City: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[name]}}}
Humidity: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[main.humidity]}}}
Pressure: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[main.pressure]}}}
Temperature: {{{$.a0828f32-34b8-4c8d-b6b3-1d82ca305921.weather.[main.temp]}}}{
"variablesPipeline": {
"scopeDepth": 1,
"rawValue": true
}
}<!DOCTYPE html>
<html>
<body>
<div id="my-am-designer" class="am-designer"></div>
<div id="my-am-flow-manager" class="am-flow-manager-container"></div>
<script src="./appmixer.js"></script>
...
</body>
</html>



































[
{
"role": "admin",
"resource": "*",
"action": [
"*"
],
"attributes": [
"non-private"
]
},
{
"role": "user",
"resource": "*",
"action": [
"*"
],
"attributes": [
"non-private"
]
},
{
"role": "tester",
"resource": "*",
"action": [
"*"
],
"attributes": [
"non-private"
]
}
][
"routes",
"components"
]['*', 'flows']['*', 'read', '!read', 'create', '!create', 'update', '!update', 'delete', '!delete'][
{
"messageId": "a9b78d3c-ec9a-4c0e-81c2-b1df12bd46d7",
"flowId": "796d7b5c-bea0-4594-a9df-a8a0e3c4616e",
"componentId": "fdb29d7b-c6b7-423b-adb2-87b41289e925",
"messages": {
"in": [
{
"properties": {
"correlationId": "0dcb7b2a-5933-481a-bb9c-c08a865656c0",
"gridInstanceId": null,
"contentType": "application/json",
"contentEncoding": "utf8",
Appmixer lets you manage the components' inspector fields through the manifest or the strings object.
{
"messageId": "a9b78d3c-ec9a-4c0e-81c2-b1df12bd46d7",
"flowId": "796d7b5c-bea0-4594-a9df-a8a0e3c4616e",
"componentId": "fdb29d7b-c6b7-423b-adb2-87b41289e925",
"messages": {
"in": [
{
"properties": {
"correlationId": "0dcb7b2a-5933-481a-bb9c-c08a865656c0",
"gridInstanceId": null,
"contentType": "application/json",
"contentEncoding": "utf8",
"sender": {
"componentId": "3961d498-83f8-4714-85ba-0539d3055892",
"type": "appmixer.utils.controls.OnStart",
"outputPort": "out"
},
"destination": {
"componentId": "fdb29d7b-c6b7-423b-adb2-87b41289e925",
"inputPort": "in"
},
"correlationInPort": null,
"componentHeaders": {},
"flowId": "796d7b5c-bea0-4594-a9df-a8a0e3c4616e",
"messageId": "12374d7e-5c66-40d1-8772-37c424bd4182",
"flowRunId": 1603726768950
},
"content": {
"key": "hello"
},
"scope": {
"3961d498-83f8-4714-85ba-0539d3055892": {
"out": {
"started": "2020-10-26T15:39:29.003Z"
}
}
},
"originalContent": {
"started": "2020-10-26T15:39:29.003Z"
}
}
]
},
"created": "2020-10-26T15:39:29.097Z",
"err": //Stringyfied error object...
}{}{}var designer = appmixer.ui.Designer({
el: '#your-designer-div',
options: {
menu: [
{ label: 'Rename', event: 'flow:rename' },
{ label: 'Insights Logs', event: 'flow:insights-logs' },
{ label: 'Clone', event: 'flow:clone' },
{ label: 'Share', event: 'flow:share' },
{ label: 'Export to SVG', event: 'flow:export-svg' },
{ label: 'Export to PNG', event: 'flow:export-png' },
{ label: 'Print', event: 'flow:print' }
]
}
});
appmixer.api.createFlow('New flow').then(function(flowId) {
designer.set('flowId', flowId);
designer.open();
}).catch(function(error) {
console.log('Something went wrong creating a new flow.', error);
});{
"name": "[vendor].[service]",
"label": "My App Label",
"category": "applications",
"categoryIndex": 2,
"index": 1,
"description": "My App Description",
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD...."
} {
"name": "[vendor].[service].[module]",
"label": "My App Label",
"category": "applications",
"categoryIndex": 2,
"index": 3,
"description": "My App Description",
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD...."
} docker-compose --project-name appmixer updocker-compose --project-name appmixer stopdocker-compose --project-name appmixer down --volumes --remove-orphans --rmi all$ ngrok http 2200
ngrok by @inconshreveable (Ctrl+C to quit)
Session Status online
Account (Plan: Basic)
Version 2.3.35
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://568284c4.ngrok.io -> http://localhost:2200
Forwarding https://568284c4.ngrok.io -> http://localhost:2200
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00 engine:
...
environment:
- APPMIXER_API_URL=${APPMIXER_API_URL:-http://localhost:2200}
...engine:
...
environment:
- APPMIXER_API_URL=https://568284c4.ngrok.io
...docker-compose --project-name appmixer down # or kill existing with Ctrl-c
docker-compose --project-name appmixer upAPPMIXER_API_URL=https://568284c4.ngrok.io docker-compose --project-name appmixer updocker-compose -p appmixer exec mongodb mongo appmixer --quiet --eval 'db.users.update({"email": "[email protected]"}, { $set: {"scope": ["user", "admin"]}})'WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })<script>
var appmixer = new Appmixer({ baseUrl: 'https://api.appmixer.com' });
</script><script>
appmixer.api.signupUser('first-username', 'first-password-123').then(function(auth) {
appmixer.set('accessToken', auth.token);
}).catch(function(err) {
alert('Something went wrong.');
});
</script><script>
var designer = appmixer.ui.Designer({ el: '#my-am-designer' });
appmixer.api.createFlow('My First Flow').then(function(flowId) {
designer.set('flowId', flowId);
designer.open();
}).catch(function(err) {
alert('Something went wrong.');
});
</script><script>
appmixer.api.authenticateUser('first-username', 'first-password-123').then(function(auth) {
appmixer.set('accessToken', auth.token);
}).catch(function(err) {
alert('Something went wrong.');
});
</script><!DOCTYPE html>
<html>
<body>
<div id="my-am-designer" class="am-designer"></div>
<div id="my-am-flow-manager" class="am-flow-manager-container"></div>
<script src="./appmixer.js"></script>
<script>
var appmixer = new Appmixer({ baseUrl: 'https://api.qa.appmixer.com' });
var yourUserUsername = '[email protected]'; // User ID of the user in YOUR system (must be in an email format!).
var appmixerUserPassword = '[POPULATE_FROM_YOUR_DB]'; // A password of the virtual user in Appmixer.
appmixer.api.authenticateUser(yourUserUsername, appmixerUserPassword).then(function(auth) {
appmixer.set('accessToken', auth.token);
onAppmixerReady();
}).catch(function(err) {
if (err.response && err.response.status === 403) {
// Virtual user not yet created in Appmixer. Create one with a random password and save the password in YOUR system
// so that you can authenticate the user later.
appmixerUserPassword = Math.random().toString(36).slice(-8);
appmixer.api.signupUser(yourUserUsername, appmixerUserPassword).then(function(auth) {
appmixer.set('accessToken', auth.token);
// ... Store auth.token and appmixerUserPassword in your DB.
onAppmixerReady();
}).catch(function(err) {
alert('Something went wrong.');
});
} else {
alert('Something went wrong.');
}
});
function onAppmixerReady() {
var designer = appmixer.ui.Designer({ el: '#my-am-designer' });
var flowManager = appmixer.ui.FlowManager({ el: '#my-am-flow-manager' });
flowManager.open();
flowManager.on('flow:open', function(flowId) {
designer.set('flowId', flowId);
flowManager.close();
designer.open();
});
flowManager.on('flow:create', function() {
flowManager.state('loader', true);
appmixer.api.createFlow('New Flow').then(function(flowId) {
designer.set('flowId', flowId);
flowManager.close();
designer.open();
}).catch(function(err) {
flowManager.state('error', 'Error creating a new flow.');
});
});
}
</script>
</body>
</html>
appmixer.api.updateFlow('9089f275-f5a5-4796-ba23-365412c5666e', {
sharedWith: [{
email: '[email protected]',
permissions: ['read', 'start', 'stop']
}]
}).then(() => {
console.log('Flow successfully shared.');
}).catch((err) => {
console.log('Something went wrong.', err);
});appmixer.api.updateFlow('9089f275-f5a5-4796-ba23-365412c5666e', {
sharedWith: []
}).then(() => {
console.log('Flow successfully unshared.');
}).catch((err) => {
console.log('Something went wrong.', err);
});appmixer.ui.FlowManager({
// ... other options ...
options: {
sharePermissions: [
{ label: 'Read', value: 'read' }
]
}
});
...
appmixer.ui.Designer({
// ... other options ...
options: {
sharePermissions: [
{ label: 'Read', value: 'read' }
]
}
});appmixer.ui.FlowManager({
// ... other options ...
options: {
shareTypes: [
{ label: 'Email', value: 'email', placeholder: 'Enter an email address' },
{ label: 'Scope', value: 'scope', placeholder: 'Enter a scope' },
{ label: 'Domain', value: 'domain', placeholder: 'Enter a domain' }
],
sharePermissions: [
{ label: 'Read', value: 'read' }
]
}
});curl -XPUT "https://api.appmixer.com/flows/9089f275-f5a5-4796-ba23-365412c5666e" -H "Content-Type: application/json" -d '{ "sharedWith": [{ "email": "[email protected]", "permissions": ["read", "start", "stop"]}] }'







{
"categories": {
"object": {
"label": "Object",
"index": 1
},
"list": {
"label": "List",
"index": 2
},
...
},
"modifiers": {
"g_stringify": {
"name": "stringify",
"label": "Stringify",
"category": [
"object",
"list"
],
"description": "Convert an object or list to a JSON string.",
"arguments": [
{
"name": "space",
"type": "number",
"isHash": true
}
],
"returns": {
"type": "string"
},
"helperFn": "function(value, { hash }) {\n\n return JSON.stringify(value, null, hash.space);\n }"
},
"g_length": {
"name": "length",
"label": "Length",
"category": [
"list",
"text"
],
"description": "Find length of text or list.",
"arguments": [],
"returns": {
"type": "number"
},
"helperFn": "function(value) {\n\n return value && value.hasOwnProperty('length') ? value.length : 0;\n }"
},
...
}
}{
"categories": {
"object": {
"label": "Object",
"index": 1
},
"list": {
"label": "List",
"index": 2
},
...
},
"modifiers": {
"g_stringify": {
"name": "stringify",
"label": "Stringify",
"category": [
"object",
"list"
],
"description": "Convert an object or list to a JSON string.",
"arguments": [
{
"name": "space",
"type": "number",
"isHash": true
}
],
"returns": {
"type": "string"
},
"helperFn": "function(value, { hash }) {\n\n return JSON.stringify(value, null, hash.space);\n }"
},
"g_length": {
"name": "length",
"label": "Length",
"category": [
"list",
"text"
],
"description": "Find length of text or list.",
"arguments": [],
"returns": {
"type": "number"
},
"helperFn": "function(value) {\n\n return value && value.hasOwnProperty('length') ? value.length : 0;\n }"
},
...
}
}{}4{
"statusCode": 400,
"error": "Bad Request",
"message": "The function returned \"undefined\""
}// Method called when a login form is submitted
function login(username, password) {
// Request a token from the engine
appmixer.api.authenticate(username, password)
.then((data) => {
// We retrieve the token from the response data object
const { token } = data;
// We set the token onto our SDK instance
appmixer.set('accessToken', token);
// Now this is where we persist our token using the
// sessionStorage. Remember that you can store the token
// pretty much on any storage system you can access from
// your application
sessionStorage.setItem('myAccessToken', token);
// After this you can redirect to home page
})
.catch((err) => {
console.log('Something wrong happened');
console.log(err);
});
}// Appmixer is initialized somewhere when your application starts
const appmixer = new Appmixer();
function initialize() {
// We look into sessionStorage using the same key we use for
// storing the token when we authenticate into our application
const token = sessionStorage.getItem('myAccessToken');
if (token){
// If token exists, we set it on the Appmixer instance
// and redirect to home page
appmixer.set('accessToken', token);
// Redirect to home page
} else {
// There is no token stored, redirect to login page
}
}// Appmixer is initialized somewhere when your application starts
const appmixer = new Appmixer();
// This is a helper function that returns if a token is expired or not
function tokenIsExpired(token){
// We assume jsonwebtoken has been imported/exposed somewhere
const decoded = jsonwebtoken.decode(token);
if (decoded && decoded.exp) {
const timeInSeconds = Math.ceil(new Date().getTime() / 1000);
return decoded.exp < timeInSeconds;
}
return true;
}
function initialize() {
// We look into sessionStorage using the same key we use for
// storing the token when we authenticate into our application
const token = sessionStorage.getItem('myAccessToken');
// Now in addition to checking if there's a stored token
// we also check if is still valid
if (token && !tokenIsExpired(token)){
// If token exists, we set it on the Appmixer instance
// and redirect to home page
appmixer.set('accessToken', token);
// Redirect to home page
} else {
// There is no token stored, redirect to login page
}
}// Appmixer is initialized somewhere when your application starts
const appmixer = new Appmixer();
appmixer.onInvalidAccessToken(function(){
// Inside this function we could redirect users to the login form,
// or if you have what's neccessary you can generate new token by
// calling the proper function (appmixer.api.authenticate) which
// will return new fresh token.
});
function initialize() {
// We look into sessionStorage using the same key we use for
// storing the token when we authenticate into our application
const token = sessionStorage.getItem('myAccessToken');
// Now in addition to checking if there's a stored token
// we also check if is still valid
if (token){
// If token exists, we set it on the Appmixer instance
// and redirect to home page
appmixer.set('accessToken', token);
// Redirect to home page
} else {
// There is no token stored, redirect to login page
}
}const instance1 = new Appmixer(...);
const instance2 = new Appmixer(...);
const persistedToken1 = sessionStorage.getItem('token1');
const persistedToken2 = sessionStorage.getItem('token2');
instance1.set('accessToken', persistedToken1);
instance2.set('accessToken', persistedToken2);{
"name": "appmixer.twilio.sms.SendSMS",
"author": "David Durman <[email protected]>",
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUwMCIgaGVp...",
"description": "Send SMS text message through Twilio.",
"private": false,
"auth": {
"service": "appmixer:twilio"
},
"outPorts": [
{
"name": "sent",
"options": [
{ "label": "Message Sid", "value": "sid" }
]
}
],
"inPorts": [
{
"name": "message",
"schema": {
"type": "object",
"properties": {
"body": { "type": "string" },
"to": { "type": "string" },
"from": { "type": "string" }
},
"required": [
"from", "to"
]
},
"inspector": {
"inputs": {
"body": {
"type": "text",
"label": "Text message",
"tooltip": "Text message that should be sent.",
"index": 1
},
"from": {
"type": "select",
"label": "From number",
"placeholder": "Type number",
"tooltip": "Select Twilio phone number.",
"index": 2,
"source": {
"url": "/component/appmixer/twilio/sms/ListFromNumbers?outPort=numbers",
"data": {
"transform": "./transformers#fromNumbersToSelectArray"
}
}
},
"to": {
"type": "text",
"label": "To number",
"tooltip": "The destination phone number. <br/><br/>Format with a '+' and country code e.g., +16175551212 (E.164 format).",
"index": 3
}
}
}
}
],
"localization": {
"cs": {
"label": "Pošli SMS",
"description": "Pošli SMS pomocí Twilia",
"inPorts[0].name": "Zpráva",
"inPorts[0].inspector.inputs.body.label": "Textová zpráva",
"inPorts[0].inspector.inputs.from.label": "Číslo volajícího",
"inPorts[0].inspector.inputs.from.placeholder": "Hledej číslo",
"outPorts[0].name": "Odesláno",
"outPorts[0].options[sid].label": "Sid zprávy"
},
"sk": {
"label": "Pošli SMS",
"description": "Pošli SMS pomocou Twilia",
"inPorts[0].name": "Správa",
"inPorts[0].inspector.inputs.body.label": "Textová správa",
"inPorts[0].inspector.inputs.from.label": "číslo volajúceho",
"outPorts[0].name": "Odoslané",
"outPorts[0].options[sid].label": "Sid správy"
}
}
}// Create an SDK instance
var appmixer = new Appmixer()
// Will use the strings under 'cs' key
appmixer.set('lang', 'cs')
// Will switch the strings to the ones under 'sk' key
appmixer.set('lang', 'sk')var appmixer = new Appmixer();
var mySkStrings = { /* Strings definition for sk language */ };
var myCsStrings = { /* Strings definition for cs language */ };
// This function will be called when the user clicks on some
// "Switch to sk" button
function setLangToSk() {
appmixer.set('lang', 'sk');
appmixer.set('strings', mySkStrings);
}
// This function will be called when the user clicks on some
// "Switch to cs" button
function setLangToCs() {
appmixer.set('lang', 'cs');
appmixer.set('strings', myCsStrings);
}{
components: {
"appmixer.twilio.sms.SendSMS": {
"inPorts[0].inspector.inputs.body.label": "Textová zpráva",
"inPorts[0].inspector.inputs.from.label": "Číslo volajícího",
"inPorts[0].inspector.inputs.from.placeholder": "Hledej číslo"
}
}
// Other namespaces (designer, storage, accounts...)
}{
"name": "appmixer.twilio",
"label": "Twilio",
"category": "applications",
"description": "Twilio is an easy tool for developers to send and receive SMS and voice calls.",
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMj...",
"localization": {
"cz": {
"label": "Modul Twilio",
"description": "Twilio je snadný nástroj pro vývojáře k odesílání a přijímání SMS a hlasových hovorů."
},
"sk": {
"label": "Modul Twilio",
"description": "Twilio je ľahký nástroj pre vývojárov na odosielanie a prijímanie SMS a hlasových hovorov."
}
}
}"appmixer.twilio": {
"label": "Modul Twilio",
"description": "Twilio je snadný nástroj pro vývojáře k odesílání a přijímání SMS a hlasových hovorů."
}"flow:rename""flow:export-svg""flow:export-png""flow:print"config.sharePermissionsAccess Data Stores (built-in key-value store).




appmixer.registerInspectorField('your-custom-text-input', {
template: `
<div class="your-custom-text-input">
<label>Name</label>
<input :value="value" @change="change($event.target.value)">
</div>
`
});appmixer.registerInspectorField('your-custom-input-type', {
template: `
<div class="your-custom-input-field">
<label>Firstname</label>
<input v-model="firstname" @change="onChange">
<label>Lastname</label>
<input v-model="lastname" @change="onChange">
</div>
`,
watch: {
value: {
immediate: true,
handler(value = '') {
this.firstname = value.split(' ')[0] || '';
this.lastname = value.split(' ')[1] || '';
},
},
},
data() {
return {
firstname: '',
lastname: '',
};
},
methods: {
onChange() {
const name = [
this.firstname.trim(),
this.lastname.trim(),
].join(' ');
this.change(name);
},
},
});
.your-custom-input-field {
font: 14px 'Segoe UI', Verdana, sans-serif;
}
.your-custom-input-field label {
display: block;
margin: 10px 0 5px;
color: #888;
}
.your-custom-input-field input {
display: block;
padding: 0.5em;
border: solid 1px #e8e8e8;
border-radius: 5px;
}var flowManager = appmixer.ui.FlowManager({
el: '#your-flow-manager-div',
options: {
menu: [
{ label: 'Edit', event: 'flow:open' },
{ label: 'Clone', event: 'flow:clone' },
{ label: 'Share', event: 'flow:share' },
{ label: 'Insights', event: 'flow:insights' },
{ label: 'Delete', event: 'flow:remove' }
]
}
});
flowManager.on('flow:start', function(flowId) {
flowManager.state('loader', true);
appmixer.api.startFlow(flowId).then(function() {
flowManager.state('loader', false);
flowManager.reload();
}).catch(function(error) {
flowManager.state('error', 'Starting flow failed.');
});
});
flowManager.on('flow:create', function() {
flowManager.state('loader', true);
appmixer.api.createFlow('New flow').then(function(flowId) {
flowManager.state('loader', false);
designer.set('flowId', flowId);
designer.open();
}).catch(function(error) {
flowManager.state('error', 'Creating flow failed.');
});
});
flowManager.open();$ curl -XPOST https://api.appmixer.com/people-task/tasks -H 'Authorization: Bearer TOKEN' -H 'Content-Type: application/json' -d '{ "approver": "[email protected]", "decisionBy": "2020-03-01 19:00:00", "description": "Example description", "requester": "[email protected]", "title": "My Task title" }'{
"approver": "[email protected]",
"decisionBy": "2020-03-01T18:00:00.000Z",
"description": "Example description",
"requester": "[email protected]",
"title": "My Task title",
"status": "pending",
"approverSecret": "75a69470a129597b9de7b97829a3501f8fb8ae43e8f818fcae4191552eb70a66",
"requesterSecret": "440197b197b9743b457172d37bcf98db3e006644657f9e19192efcc428125aae",
"created":"2020-02-23T14:39:14.796Z",
"id": "5e528e92095eeb0008b2aa40"
}$ curl -XPOST https://api.appmixer.com/people-task/webhooks -H 'Authorization: Bearer TOKEN' -H 'Content-Type: application/json' -d '{ "url": "https://mywebhooks.myserver.com", "taskId": "5e528e92095eeb0008b2aa40" }'{
"id": "5e528e92095eeb0008b2aa40",
"title": "My Task title",
"description": "Example description",
"status": "approved",
"approver": "[email protected]",
"requester": "[email protected]",
"decisionBy": "2020-03-01T18:00:00.000Z",
"decisionMade": "2020-02-23T14:46:39.175Z",
"created": "2020-02-23T14:45:22.669Z",
"mtime": "2020-02-23T14:45:22.669Z"
}$ curl -XGET https://api.appmixer.com/people-task/tasks -H 'Authorization: Bearer TOKEN'[
{
"id": "5e528de61fb74b0008d524e8",
"title": "My Task title",
"description": "Example description",
"status": "pending",
"approver": "[email protected]",
"requester": "[email protected]",
"decisionBy": "2020-03-01T18:00:00.000Z",
"created": "2020-02-23T14:36:22.730Z",
"mtime": "2020-02-23T14:36:22.732Z",
"isApprover": true
},
{
"id": "5e529002095eeb0008b2aa41",
"title": "My Task title",
"description": "Example description",
"status": "approved",
"approver": "[email protected]",
"requester": "[email protected]",
"decisionBy": "2020-03-01T18:00:00.000Z",
"decisionMade": "2020-02-23T14:46:55.290Z",
"created": "2020-02-23T14:45:22.669Z",
"mtime": "2020-02-23T14:46:55.293Z",
"isApprover": true
}
]$ curl -XGET "https://api.appmixer.com/people-task/tasks?secret=75a69470a129597b9de7b97829a3501f8fb8ae43e8f818fcae4191552eb70a66"
$ curl -XPUT https://api.qa.appmixer.com/people-task/tasks/5e528de61fb74b0008d524e8/approve -H 'Authorization: Bearer TOKEN'$ curl -XPUT https://api.qa.appmixer.com/people-task/tasks/5e528de61fb74b0008d524e8/reject -H 'Authorization: Bearer TOKEN'$ curl -XPUT https://api.qa.appmixer.com/people-task/tasks/5e528de61fb74b0008d524e8/approve -H 'Authorization: Bearer TOKEN' -H 'Content-Type: application/json' -d '{ "secret": "75a69470a129597b9de7b97829a3501f8fb8ae43e8f818fcae4191552eb70a66" }'
var peopleTasks = appmixer.ui.PeopleTasks({ el: '#your-people-tasks' });
peopleTasks.set('secret', '7crn14y8ew7a2c45b413ed7a0788175e764c4a7d11d44289bd2706e09ea4318f');
peopleTasks.open();appmixer.api.approveTask('5e529002095eeb0008b2aa41' /*task ID*/, {
secret: '7crn14y8ew7a2c45b413ed7a0788175e764c4a7d11d44289bd2706e09ea4318f'
});
// or
appmixer.api.rejectTask('5e529002095eeb0008b2aa41' /*task ID*/, {
secret: '7crn14y8ew7a2c45b413ed7a0788175e764c4a7d11d44289bd2706e09ea4318f'
});{
"categories": {
"object": {
"label": "Object",
"index": 1
},
"list": {
"label": "List",
"index": 2
},
...
},
"modifiers": {
"g_stringify": {
"name": "stringify",
"label": "Stringify",
"category": [
"object",
"list"
],
"description": "Convert an object or list to a JSON string.",
"arguments": [
{
"name": "space",
"type": "number",
"isHash": true
}
],
"returns": {
"type": "string"
},
"helperFn": "function(value, { hash }) {\n\n return JSON.stringify(value, null, hash.space);\n }"
},
"g_length": {
"name": "length",
"label": "Length",
"category": [
"list",
"text"
],
"description": "Find length of text or list.",
"arguments": [],
"returns": {
"type": "number"
},
"helperFn": "function(value) {\n\n return value && value.hasOwnProperty('length') ? value.length : 0;\n }"
},
...
}
}"formatDate": {
"label": "Format Date",
"category": ["date"],
"description": "Transforms the value into given date format",
"arguments": [
{ "name": "Format", "type": "string", "isHash": false }
],
"returns": {
"type": "string"
},
"helperFn": "function(value, format, { helpers }) { return helpers.moment(value).format(format) }"
}"formatDate": {
"label": "Format Date",
"category": ["date"],
"description": "Transforms the value into given date format",
"arguments": [
{ "name": "format", "type": "string", "isHash": true }
],
"returns": {
"type": "string"
},
"helperFn": "function(value, format, { hash: { format }, helpers }) { return helpers.moment(value).format(format) }"
}{
"categories": {
"object": {
"label": "Object",
"index": 1
},
"list": {
"label": "List",
"index": 2
},
...
},
"modifiers": {
"g_stringify": {
"name": "stringify",
"label": "Stringify",
"category": [
"object",
"list"
],
"description": "Convert an object or list to a JSON string.",
"arguments": [
{
"name": "space",
"type": "number",
"isHash": true
}
],
"returns": {
"type": "string"
},
"helperFn": "function(value, { hash }) {\n\n return JSON.stringify(value, null, hash.space);\n }"
},
"g_length": {
"name": "length",
"label": "Length",
"category": [
"list",
"text"
],
"description": "Find length of text or list.",
"arguments": [],
"returns": {
"type": "number"
},
"helperFn": "function(value) {\n\n return value && value.hasOwnProperty('length') ? value.length : 0;\n }"
},
...
// At some place inside the object
"formatDate": {
"label": "Format Date",
"category": ["date"],
"description": "Transforms the value into given date format",
"arguments": [
{ "name": "Format", "type": "string", "isHash": false }
],
"returns": {
"type": "string"
},
"helperFn": "function(value, format, { helpers }) { return helpers.moment(value).format(format) }"
}
}
}

























[{
"name": "My Store 1",
"storeId": "5c6fc9932ff3ff000747ead4"
}, {
"name": "My Store 2",
"storeId": "2a3fc9512bb3fca23747lai2"
}]{
"name": "My Store 1",
"storeId": "5c6fc9932ff3ff000747ead4"
}{
"count": 681
}[{
"key":"Box 8RE1",
"storeId":"5b214ba6f90a6200113abfd8",
"userId":"583c06511afb7b0016ef120b",
"updatedAt":"2019-03-06T10:02:20.419Z",
"value":"321",
"createdAt":"2019-03-06T10:02:20.419Z"
},{
"key":"T-shirt T41B",
"storeId":"5b214ba6f90a6200113abfd8",
"userId":"583c06511afb7b0016ef120b",
"updatedAt":"2019-03-06T10:01:59.360Z",
"value":"18",
"createdAt":"2019-03-06T10:01:59.360Z"
},{
"key":"T-shirt A12C",
"storeId":"5b214ba6f90a6200113abfd8",
"userId":"583c06511afb7b0016ef120b",
"updatedAt":"2019-03-06T10:01:45.204Z",
"value":"12",
"createdAt":"2019-03-06T10:01:45.204Z"
}]"5c7f9bfe51dbaf0007f08db0"{
"oldName":"My Old Store Name",
"storeId":"5c7f9bfe51dbaf0007f08db0"
}{
"key":"mykey",
"value":"myvalue",
"createdAt":"2019-03-06T10:17:58.796Z",
"updatedAt":"2019-03-06T10:17:58.796Z"
}{
"deletedCount": 1
}triggeractionVerticaltriggerVertical{
chartId: '5defb3901f17d98d974fbb00'
} engine:
build:
context: .
dockerfile: Dockerfile
depends_on:
- mongodb
- rabbitmq
- elasticsearch
- redis
environment:
# this variable has to point to your Webhook
- WEBHOOK_FLOW_COMPONENT_ERROR=https://your.example.com/error-handling-ur




Get list of all messages passing through your flows and usage information (telemetry).
var appmixer = new Appmixer({
componentShapes: {
action: myCustomComponentShape,
trigger: myCustomComponentShape,
myUniqueShape: myCustomComponentShape
},
});{
shape: "myUniqueShape"
}var myCustomComponentShape = {
attributes: {
size: { height: 60, width: 60 },
markup: [{
tagName: 'rect',
selector: 'body',
}, {
tagName: 'text',
selector: 'label'
}, {
tagName: 'image',
selector: 'icon',
}],
attrs: {
body: {
event: 'remove', // Click on the "body" will remove the element
refWidth: '100%',
refHeight: '100%',
stroke: 'black',
strokeWidth: '4px',
fill: 'white',
},
label: {
ref: 'body',
textAnchor: 'middle',
refX: 0.5,
refY: '100%',
refY2: 12,
fill: 'black',
fontFamily: 'Helvetica, Arial, sans-serif',
},
icon: {
ref: 'body',
refX: 0.5,
refY: 0.5,
xAlignment: 'middle',
yAlignment: 'middle',
height: 30,
width: 30,
},
},
},
ports: {
attributes: {
in: {
attrs: {
'.port-label': {
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: 12,
},
},
},
out: {
attrs: {
'.port-label': {
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: 12,
},
},
},
},
},
link: {
attributes: {
router: {
name: 'metro',
},
},
attrs: {
line: {
event: "remove", // Click on the line will remove the link
},
},
},
states: {
'@active': {
attributes: {
attrs: {
body: {
stroke: 'blue',
},
},
},
link: {
attributes: {
attrs: {
line: {
stroke: 'blue',
},
},
},
},
},
},
};{
chartId: "5defa30cbd1ca06288202346"
index: 1
mtime: "2019-12-10T13:52:12.288Z"
name: "Updated Chart"
options: {,…}
query: {,…}
traces: {,…}
type: "bar"
userId: "5dee76c19462fe6b3fd42d79"
}[
{
"chartId": "5defa30cbd1ca06288202346",
"userId": "5dee76c19462fe6b3fd42d79",
"name": Chart 1",
"index": 0,
"type": "bar",
"query": { ... },
"options": { ... },
"traces": { ... },
"mtime": "2019-12-10T13:52:12.288Z"
},
{
"chartId": "5defa30cbd1ca06288202347",
"userId": "5dee76c19462fe6b3fd42d79",
"name": Chart 2",
"index": 0,
"type": "bar",
"query": { ... },
"options": { ... },
"traces": { ... },
"mtime": "2019-13-10T13:52:12.288Z"
}
]{
"chartId": "5defa30cbd1ca06288202346",
"userId": "5dee76c19462fe6b3fd42d79",
"name": Chart 1",
"index": 0,
"type": "bar",
"query": { ... },
"options": { ... },
"traces": { ... },
"mtime": "2019-12-10T13:52:12.288Z"
}{
"name":"Test create chart",
"index":1,
"type":"bar",
"options":{
"layout":{
"showlegend":true,
"xaxis":{
"showline":true
},
"yaxis":{
"showline":false
},
"barmode":"group",
"bargap":0.2,
"bargroupgap":0.1
},
"showgrid":true,
"showticklabels":true,
"horizontal":false
},
"query":{
"range":{
"from":{
"endOf":null,
"startOf":"day",
"subtract":[7,"day"]
},
"to":{}
}
},
"traces":{
"2f985875-4149-4c7b-a4ab-e204269c0c0f":{
"name":"Trace 1",
"hidden":false,
"agg":{
"date_histogram":{
"interval":"1d",
"min_doc_count":"0",
"field":"@timestamp"
}
},
"options":{
"type":"bar",
"linewidth":0,
"opacity":1
},
"source":{
"type":"flow",
"targets":{
"dbd206a4-23b3-44a4-a6c4-59db74aa3fb5":[]
}
}
}
}
}var wizard = appmixer.ui.Wizard({
el: '#your-wizard-div',
flowId: 'your-integration-id',
});
wizard.on('flow:start', function(flowId) {
wizard.close();
});
wizard.open();{
"err": {
"message": "Validation error on ports: in",
"error": {
"in": [
[
{
"keyword": "format",
"dataPath": ".to",
"schemaPath": "#/properties/to/format",
"params": {
"format": "email"
},
"message": "should match format \"email\""
}
]
]
},
"code": "GRID_ERR_VAL_PORTS",
"name": "ValidationFlowError",
"stack": "ValidationFlowError: Validation error on ports: in\n at MessagesProcessor.process (/Users/martinkrcmar/Programming/client/appmixer/appmixer-core/engine/src/context/MessagesProcessor.js:67:19)\n at Context.prepare (/Users/martinkrcmar/Programming/client/appmixer/appmixer-core/engine/src/context/Context.js:68:31)\n at ContextHandler.createContext (/Users/martinkrcmar/Programming/client/appmixer/appmixer-core/engine/src/context/ContextHandler.js:60:17)\n at async InputQueue.consume (/Users/martinkrcmar/Programming/client/appmixer/appmixer-core/engine/src/InputQueue.js:151:23)\n at async InputQueue.onMessage (/Users/martinkrcmar/Programming/client/appmixer/appmixer-core/engine/src/AMQPClient.js:378:20)"
},
"flowId": "32f605b2-8fbe-4f68-9db9-ce182b35c159",
"flowName": "New flow",
"userId": "5f804b96ea48ec47a8c444a7",
"componentId": "0bb33e42-fbc4-464e-98f1-459f1ff626ac",
"componentType": "appmixer.utils.email.SendEmail",
"inputMessages": {
"in": [
{
"properties": {
"correlationId": "339bc448-a806-4e61-8d38-4211fcedaf12",
"gridInstanceId": null,
"contentType": "application/json",
"contentEncoding": "utf8",
"sender": {
"componentId": "e8c581b4-9985-4f2c-bf30-895bf1d5541b",
"type": "appmixer.utils.controls.OnStart",
"outputPort": "out"
},
"destination": {
"componentId": "0bb33e42-fbc4-464e-98f1-459f1ff626ac",
"inputPort": "in"
},
"correlationInPort": null,
"componentHeaders": {},
"flowId": "32f605b2-8fbe-4f68-9db9-ce182b35c159",
"messageId": "47293e2c-4e33-4558-8805-386de392ef04",
"flowRunId": 1607683798995
},
"content": {
"to": "2020-12-11T10:49:59.050Z"
},
"scope": {
"e8c581b4-9985-4f2c-bf30-895bf1d5541b": {
"out": {
"started": "2020-12-11T10:49:59.050Z"
}
}
},
"originalContent": {
"started": "2020-12-11T10:49:59.050Z"
}
}
]
},
"accessTokenId": null
}{
"err": {
"stack": "Error: getaddrinfo ENOTFOUND mandrillapp.com\n at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26)",
"message": "getaddrinfo ENOTFOUND mandrillapp.com",
"errno": "ENOTFOUND",
"code": "ENOTFOUND",
"syscall": "getaddrinfo",
"hostname": "mandrillapp.com",
"name": "Error"
},
"flowId": "0ab287ef-7bd6-4cc3-b53b-c916c857cbe7",
"flowName": "Invalid email test",
"userId": "5fd744d5e9ed7d0011ca35f9",
"componentId": "cb3f4ff5-7b6e-4d24-b7a8-2115c8254baa",
"componentType": "appmixer.utils.email.SendEmail",
"inputMessages": {
"in": [
{
"properties": {
"correlationId": "254ad628-f9c1-4483-81ed-33a22ac3ddc6",
"gridInstanceId": null,
"contentType": "application/json",
"contentEncoding": "utf8",
"sender": {
"componentId": "bcbeda1d-9036-45af-a25d-a57cf06e3f90",
"type": "appmixer.utils.controls.OnStart",
"outputPort": "out"
},
"destination": {
"componentId": "cb3f4ff5-7b6e-4d24-b7a8-2115c8254baa",
"inputPort": "in"
},
"correlationInPort": null,
"componentHeaders": {},
"flowId": "0ab287ef-7bd6-4cc3-b53b-c916c857cbe7",
"messageId": "33b9fcbf-b326-4eb4-bba6-cf34979f4ba2",
"flowRunId": 1607944841843,
"quotaId": "qs-4882d03d-65e1-44dc-983e-d2a33071779d"
},
"content": {
"to": [
{
"email": "[email protected]",
"type": "to"
}
],
"from_email": "[email protected]"
},
"scope": {
"bcbeda1d-9036-45af-a25d-a57cf06e3f90": {
"out": {
"started": "2020-12-14T11:20:41.858Z"
}
}
},
"originalContent": {
"started": "2020-12-14T11:20:41.858Z"
}
}
]
},
"accessTokenId": null
}



{
"buckets": [
{
"key_as_string": "2018-04-16T00:00:00.000Z",
"key": 1523836800000,
"doc_count": 35
},
{
"key_as_string": "2018-04-17T00:00:00.000Z",
"key": 1523923200000,
"doc_count": 60
}
],
"hits": [
{
"severity": "info",
"componentType": "appmixer.slack.list.SendChannelMessage",
"componentId": "a1cda3ff-8e20-41df-8e7d-8e52419e6d17",
"portType": "in",
"senderId": "c062e744-2de1-4c80-afce-713be3145315",
"@timestamp": "2018-04-06T14:02:04.517Z",
"port": "message",
"senderType": "appmixer.utils.controls.OnStart",
"correlationId": "a5128135-3a23-4837-92f8-9dc099ff0700",
"id": "339d216c-48e0-4110-9210-a4c176b30f84:a1cda3ff-8e20-41df-8e7d-8e52419e6d17:input-queue",
"gridTimestamp": "2018-04-06T14:02:04.472Z",
"flowId": "339d216c-48e0-4110-9210-a4c176b30f84",
"entity": "input-queue",
"_id": "AWKbQ6Vr9I6rzDWu4NbG",
"_index": "appmixer-201804"
},
{
"severity": "info",
"componentType": "appmixer.slack.list.SendChannelMessage",
"componentId": "a1cda3ff-8e20-41df-8e7d-8e52419e6d17",
"portType": "in",
"senderId": "c062e744-2de1-4c80-afce-713be3145315",
"@timestamp": "2018-04-03T20:22:10.971Z",
"port": "message",
"senderType": "appmixer.utils.controls.OnStart",
"correlationId": "7ed0bbb4-0b05-4469-8168-401cd909e5d2",
"id": "339d216c-48e0-4110-9210-a4c176b30f84:a1cda3ff-8e20-41df-8e7d-8e52419e6d17:input-queue",
"gridTimestamp": "2018-04-03T20:22:10.927Z",
"flowId": "339d216c-48e0-4110-9210-a4c176b30f84",
"entity": "input-queue",
"_id": "AWKNLJEg9I6rzDWu3F8E",
"_index": "appmixer-201804"
}
]
}{
"_index": "appmixer-201804",
"_type": "engine",
"_id": "AWKbQ6Vr9I6rzDWu4NbG",
"_version": 1,
"_source": {
"severity": "info",
"msg": {
"text": "Hey Slack!"
},
"componentType": "appmixer.slack.list.SendChannelMessage",
"componentId": "a1cda3ff-8e20-41df-8e7d-8e52419e6d17",
"bundleId": "86a83327-1b13-4cab-a7cd-bbcce5f2402d",
"portType": "in",
"senderId": "c062e744-2de1-4c80-afce-713be3145315",
"@timestamp": "2018-04-06T14:02:04.517Z",
"port": "message",
"@version": "1",
"senderType": "appmixer.utils.controls.OnStart",
"correlationId": "a5128135-3a23-4837-92f8-9dc099ff0700",
"id": "339d216c-48e0-4110-9210-a4c176b30f84:a1cda3ff-8e20-41df-8e7d-8e52419e6d17:input-queue",
"gridTimestamp": "2018-04-06T14:02:04.472Z",
"flowId": "339d216c-48e0-4110-9210-a4c176b30f84"
}
}{
"aggregations": {
"avg_price": {
"value": 10
},
"sum_income": {
"value": 2000
}
},
"hits": [
{ "flowId": "78230318-37b8-40ac-97a5-996ba9a6c48f", ... },
{ "flowId": "78230318-37b8-40ac-97a5-996ba9a6c48f", ... },
...
]
}{
"messageCounts": {
"from": "2018-03-17",
"to": "2018-04-17",
"count": 348,
"userId": "58593f07c3ee4f239dc69ff7"
},
"runningFlows": {
"userId": "58593f07c3ee4f239dc69ff7",
"count": 4
},
"activeConnectors": {
"userId": "58593f07c3ee4f239dc69ff7",
"count": 8
},
"usedApps": [
"appmixer.utils",
"appmixer.slack",
"appmixer.asana",
"appmixer.salesforce",
"appmixer.twilio"
]
}@unchecked- the component is selected
# Install and initialize the Appmixer CLI tool if you haven't done so already:
$ npm install -g appmixer
$ appmixer url http://localhost:2200
# Use e.g. the same user as you signed-up with in the Getting Started guide.
$ appmixer login [email protected]
$ appmixer publish appmixer.myservice.zip$ tree myservice/
myservice/
├── mymodule
│ └── HelloAppmixer
│ ├── component.json
│ └── HelloAppmixer.js
└── service.json
2 directories, 3 filesmodule.exports = {};{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg=="
}{
"name": "appmixer.myservice",
"label": "My Service",
"category": "applications",
"description": "My Custom App",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg=="
}# Initialize your Appmixer CLI client if you haven't done that already:
$ npm install -g appmixer
$ appmixer url http://localhost:2200
# Use e.g. the same user as you signed-up with in the Getting Started guide.
$ appmixer login [email protected]
# Now we can pack and publish our component:
$ appmixer pack myservice
$ appmixer publish appmixer.myservice.zip{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg==",
"inPorts": [
{
"name": "in"
}
]
}{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg==",
"inPorts": [
{
"name": "in",
"schema": {
"type": "object",
"properties": {
"text": { "type": "string" },
"count": { "type": "number" }
},
"required": ["text"]
},
"inspector": {
"inputs": {
"text": {
"type": "text",
"group": "transformation",
"label": "Text",
"index": 1
},
"count": {
"type": "number",
"group": "transformation",
"label": "Count",
"index": 2
}
},
"groups": {
"transformation": {
"label": "Transformation"
}
}
}
}
]
}$ appmixer pack myservice
$ appmixer publish myservice.zip{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg==",
"inPorts": [
{
"name": "in",
"schema": {
"type": "object",
"properties": {
"text": { "type": "string" },
"count": { "type": "number" }
},
"required": ["text"]
},
"inspector": {
"inputs": {
"text": {
"type": "text",
"group": "transformation",
"label": "Text",
"index": 1
},
"count": {
"type": "number",
"group": "transformation",
"label": "Count",
"index": 2
}
},
"groups": {
"transformation": {
"label": "Transformation"
}
}
}
}
],
"outPorts": [
{
"name": "out",
"options": [
{ "label": "Hello Data", "value": "mydata" }
]
}
]
}$ cd myservice/mymodule/HelloAppmixer/
$ npm init -y
$ npm install axios --saveconst axios = require('axios');
module.exports = {
receive(context) {
let count = context.messages.in.content.count || 1;
let text = context.messages.in.content.text;
return axios.get('https://postman-echo.com/get?text=' + text + '&count=' + count)
.then(response => {
return context.sendJson({
mydata: 'Received from Postman echo: ' + JSON.stringify(response.data.args)
}, 'out');
});
}
};








{
"appmixer.asana": {
"name": "appmixer.asana",
"label": "Asana",
"category": "applications",
"description": "Asana is a collaborative information manager for workspace. It helps you organize people and tasks effectively.",
"icon": "data:image/png;base64,iVBORw0KGgoA....kJggg=="
},
"appmixer.calendly": {
"name": "appmixer.calendly",
"label": "Calendly",
"category": "applications",
"description": "Calendly helps you schedule meetings without the back-and-forth emails. It does not work with the free Basic account. It works with Premium or Pro account.",
"icon": "data:image/png;base64,iVBORw0KGgoA....kJggg=="
},
"appmixer.clearbit": {
"name": "appmixer.clearbit",
"label": "Clearbit",
"category": "applications",
"description": "Clearbit is a data API that lets you enrich your person and company records with social, demographic, and firmographic data.",
"icon": "data:image/png;base64,iVBORw0KGgoA....kSuQmCC"
},
"appmixer.dropbox": {
"name": "appmixer.dropbox",
"label": "Dropbox",
"category": "applications",
"description": "Dropbox is a home for all your photos, documents, videos, and other files. Dropbox lets you access your stuff from anywhere and makes it easy to share with others.",
"icon": "data:image/svg+xml;base64,PHN2Z....3N2Zz4="
},
"appmixer.evernote": {
"name": "appmixer.evernote",
"label": "Evernote",
"category": "applications",
"description": "Evernote is a powerful note taking application that makes it easy to capture ideas, images, contacts, and anything else you need to remember. Bring your life's work together in one digital workspace, available on all major mobile platforms and devices.",
"icon": "data:image/png;base64,iVBORw0KGgoA....kSuQmCC"
}
}[
{
"name": "appmixer.twilio.sms.SendSMS",
"author": "David Durman <[email protected]>",
"icon": "data:image/png;base64,iVBORw...gg==",
"description": "Send SMS text message through Twilio.",
"auth": { "service": "appmixer:twilio" },
"inPorts": [
{
"name": "message",
"schema": {
"type": "object",
"properties": {
"body": { "type": "string" },
"to": { "type": "string" }
},
"required": [ "to" ]
},
"inspector": {
"inputs": {
"body": {
"type": "text",
"label": "Text message",
"tooltip": "Text message that should be sent.",
"index": 1
},
"to": {
"type": "text",
"label": "To number",
"tooltip": "The destination phone number. <br/><br/>Format with a '+' and country code e.g., +16175551212 (E.164 format).",
"index": 2
}
}
}
}
],
"properties": {
"schema": {
"properties": {
"fromNumber": { "type": "string" }
},
"required": [ "fromNumber" ]
},
"inspector": {
"inputs": {
"fromNumber": {
"type": "select",
"label": "From number",
"tooltip": "Select Twilio phone number.",
"index": 1,
"source": {
"url": "/component/appmixer/twilio/sms/ListFromNumbers?outPort=numbers",
"data": {
"transform": "./transformers#fromNumbersToSelectArray"
}
}
}
}
}
}
},
{
"name": "appmixer.twilio.calls.NewCall",
"author": "David Durman <[email protected]>",
"icon": "data:image/png;base64,iVBORw...gg==",
"description": "Receive a call through Twilio.",
"auth": { "service": "appmixer:twilio" },
"webhook": true,
"webhookAsync": true,
"outPorts": [
{
"name": "call",
"options": []
}
],
"properties": {
"schema": {
"properties": {
"generateInspector": { "type": "boolean" },
"url": {}
}
},
"inspector": {
"inputs": {
"url": {
"source": {
"url": "/component/appmixer/twilio/calls/NewCall?outPort=call",
"data": {
"properties": {
"generateInspector": true
}
}
}
}
}
}
}
}
][
"appmixer.asana.projects.CreateProject",
"appmixer.asana.projects.NewProject",
"appmixer.asana.tasks.CreateStory",
"appmixer.calendly.events.InviteeCanceled",
"appmixer.calendly.events.InviteeCreated",
"appmixer.clearbit.enrichment.FindCompany",
"appmixer.clearbit.enrichment.FindPerson"
]{
"ticket":"a194d145-3768-4a8a-84a4-4f1e4e08c4ad"
}// Successful upload. No errors:
{
"finished": "2020-02-28T15:57:34.549Z"
}
// Upload finished. Errors encountered:
{
"finished": "2020-02-28T15:25:39.515Z",
"err": "Invalid schema for appmixer/utils/service.json",
"data": [
{
"keyword": "required",
"dataPath": "",
"schemaPath": "#/required",
"params": {
"missingProperty": "label"
},
"message": "should have required property 'label'"
},
{
"keyword": "required",
"dataPath": "",
"schemaPath": "#/required",
"params": {
"missingProperty": "description"
},
"message": "should have required property 'description'"
}
]
}GET https://api.appmixer.com/people-task/tasks [{
"approver": "[email protected]"
{
"count": 23
}{
"approver": "[email protected]",
"decisionBy": "2020-01-01 19:00:00",
"description": "Example description",
"requester": "[email protected]",
"status": "pending",
"title": "Example title",
"id": "5da9ed9ff29cd51c5fa27380"
}{
"approver": "[email protected]",
"decisionBy": "2020-01-01 19:00:00",
"description": "Example description",
"requester": "[email protected]",
"status": "pending",
"public": true,
"title": "Example title",
"approverSecret": "3dbd67d6db7a2c45b413ed7a0788175e764c4a7d11d44289bd2706e09ea4318f",
"requesterSecret": "440197b197b9743b457172d37bcf98db3e006644657f9e19192efcc428125aae",
"id": "5da9ed9ff29cd51c5fa27380"
}{
"url": "https://api.appmixer.com/flows/551945f2-6bbb-4ea4-a792-fb3635943372/components/a54b47fa-e7ce-463f-87bc-715ceb612947?correlationId=ee325876-4b3e-4537-9a12-58b4929f6cd8&correlationInPort=task",
"taskId": "1234",
"status": "pending",
"id": "5da9ee4cf29cd51c5fa27381"
}{
"approver": "[email protected]",
"decisionBy": "2020-01-01 19:00:00",
"description": "Example description",
"requester": "[email protected]",
"status": "pending",
"title": "Example title",
"id": "5da9ed9ff29cd51c5fa27380"
}{
"id": "5da9ed9ff29cd51c5fa27380",
"approver": "[email protected]",
"decisionBy": "2020-01-01 19:00:00",
"description": "Example description",
"requester": "[email protected]",
"status": "approved",
"title": "Example title"
}{
"id": "5da9ed9ff29cd51c5fa27380",
"approver": "[email protected]",
"decisionBy": "2020-01-01 19:00:00",
"description": "Example description",
"requester": "[email protected]",
"status": "rejected",
"title": "Example title"
}twilio/
├── auth.js
├── package.json
├── service.json
└── sms
├── ListFromNumbers
│ ├── ListFromNumbers.js
│ ├── component.json
│ ├── package.json
│ └── transformers.js
└── SendSMS
├── SendSMS.js
├── component.json
└── package.json






const twilio = require('twilio');
module.exports = {
receive(context) {
let { accountSID, authenticationToken } = context.auth;
let client = twilio(accountSID, authenticationToken);
let message = context.messages.message.content;
return client.messages.create({
body: message.body,
to: message.to,
from: message.from
}).then(message => {
return context.sendJson(message, 'sent');
});
}
};{
"name": "appmixer.twilio.sms.SendSMS",
"author": "David Durman <[email protected]>",
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUwMCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PGcgZmlsbD0iI0NGMjcyRCI+PHBhdGggZD0iTTEyNy44NiAyMjIuMzA0Yy01Mi4wMDUgMC05NC4xNjQtNDIuMTU5LTk0LjE2NC05NC4xNjMgMC01Mi4wMDUgNDIuMTU5LTk0LjE2MyA5NC4xNjQtOTQuMTYzIDUyLjAwNCAwIDk0LjE2MiA0Mi4xNTggOTQuMTYyIDk0LjE2MyAwIDUyLjAwNC00Mi4xNTggOTQuMTYzLTk0LjE2MiA5NC4xNjN6bTAtMjIyLjAyM0M1Ny4yNDUuMjgxIDAgNTcuNTI3IDAgMTI4LjE0MSAwIDE5OC43NTYgNTcuMjQ1IDI1NiAxMjcuODYgMjU2YzcwLjYxNCAwIDEyNy44NTktNTcuMjQ0IDEyNy44NTktMTI3Ljg1OSAwLTcwLjYxNC01Ny4yNDUtMTI3Ljg2LTEyNy44Ni0xMjcuODZ6Ii8+PHBhdGggZD0iTTEzMy4xMTYgOTYuMjk3YzAtMTQuNjgyIDExLjkwMy0yNi41ODUgMjYuNTg2LTI2LjU4NSAxNC42ODMgMCAyNi41ODUgMTEuOTAzIDI2LjU4NSAyNi41ODUgMCAxNC42ODQtMTEuOTAyIDI2LjU4Ni0yNi41ODUgMjYuNTg2LTE0LjY4MyAwLTI2LjU4Ni0xMS45MDItMjYuNTg2LTI2LjU4Nk0xMzMuMTE2IDE1OS45ODNjMC0xNC42ODIgMTEuOTAzLTI2LjU4NiAyNi41ODYtMjYuNTg2IDE0LjY4MyAwIDI2LjU4NSAxMS45MDQgMjYuNTg1IDI2LjU4NiAwIDE0LjY4My0xMS45MDIgMjYuNTg2LTI2LjU4NSAyNi41ODYtMTQuNjgzIDAtMjYuNTg2LTExLjkwMy0yNi41ODYtMjYuNTg2TTY5LjQzMSAxNTkuOTgzYzAtMTQuNjgyIDExLjkwNC0yNi41ODYgMjYuNTg2LTI2LjU4NiAxNC42ODMgMCAyNi41ODYgMTEuOTA0IDI2LjU4NiAyNi41ODYgMCAxNC42ODMtMTEuOTAzIDI2LjU4Ni0yNi41ODYgMjYuNTg2LTE0LjY4MiAwLTI2LjU4Ni0xMS45MDMtMjYuNTg2LTI2LjU4Nk02OS40MzEgOTYuMjk4YzAtMTQuNjgzIDExLjkwNC0yNi41ODUgMjYuNTg2LTI2LjU4NSAxNC42ODMgMCAyNi41ODYgMTEuOTAyIDI2LjU4NiAyNi41ODUgMCAxNC42ODQtMTEuOTAzIDI2LjU4Ni0yNi41ODYgMjYuNTg2LTE0LjY4MiAwLTI2LjU4Ni0xMS45MDItMjYuNTg2LTI2LjU4NiIvPjwvZz48L3N2Zz4=",
"description": "Send SMS text message through Twilio.",
"auth": {
"service": "appmixer:twilio"
},
"outPorts": [
{
"name": "sent",
"options": [
{ "label": "Message Sid", "value": "sid" }
]
}
],
"inPorts": [
{
"name": "message",
"schema": {
"type": "object",
"properties": {
"body": { "type": "string" },
"to": { "type": "string" },
"from": { "type": "string" }
},
"required": [
"from", "to"
]
},
"inspector": {
"inputs": {
"body": {
"type": "text",
"label": "Text message",
"tooltip": "Text message that should be sent.",
"index": 1
},
"from": {
"type": "select",
"label": "From number",
"tooltip": "Select Twilio phone number.",
"index": 2,
"source": {
"url": "/component/appmixer/twilio/sms/ListFromNumbers?outPort=numbers",
"data": {
"transform": "./transformers#fromNumbersToSelectArray"
}
}
},
"to": {
"type": "text",
"label": "To number",
"tooltip": "The destination phone number. <br/><br/>Format with a '+' and country code e.g., +16175551212 (E.164 format).",
"index": 3
}
}
}
}
]
}
{
"name": "appmixer.twilio.sms.SendSMS",
"version": "1.0.0",
"main": "SendSMS.js",
"author": "David Durman <[email protected]>",
"dependencies": {
"twilio": "^3.14.0"
}
}{
"name": "appmixer.twilio",
"label": "Twilio",
"category": "applications",
"description": "Twilio is a cloud communications platform as a service.",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAPEElEQVR42u2df5RVVRXHP+85MyA/hBBLkHO1pGsqCqL9wsRqlSmWUVFqGEiR/VyZQVlp1kL7g5X9tlZqRZpkJrmszFr2a6EtTE1+iGAdMexcbMxAMkdkBpjpj7Mv3m5v3rvn3PNm3iB7rbfWc+Tdc87+3rN/nb33qdAClEQxyuj83zqA4UAHcChwAhDLZxLwQmA8cCAwTH7WDTwLbAWeALYAWj5rgMeBHmCnMrqn0RwGgyqtBEQSxQcDRwNTgOnyOU5ACUE9wHpgtXweBB5SRm9rFWAqLQLE6cBcYCpwGDBugKbyJPAYsA5Yroz+9WADUxksIJIoHg18GLgYeAGtQduBpcC3ldFPDwYwAw3IMGAysAC4EGijNWk38HVgGbBJGd09UANXm70jMt9nAtcC9wKLWhgMZG6LZK7Xytz/b01DZoekk1ZGk0TxGOC7wCxgBEOTdgC3AwuV0U9l19fSgOSAaAfOA65p8d3gKsouAG5QRu9qBjCVkGBkFPZ04HPAbPZNuhW4XBm9OrTiDy6ykiheDHwCmMC+TZ3AV5TRV7aUyMoouQ4RT/MG2+EcQOoDrhcx1hNCfFXLAiETmAj8AZj/PAIjfaHny9onZkT2oO6QU4HrgMN5ftPfgfnK6JUDLrJSJZZE8euBm7BBvmbTLuA+YC3wF+BR4J9Al4iOdD2jgBcBRwAvA6YBLwfaB2COW4GzldG/91X0FR8g5PvLgT81ybnsBfYI028AViijNzZyPuvJ8CSKjwHmiCl+BHBAE+f+KmX0fT4WmO8OeT3wmyYs6D/AA8AdwI3K6E1NclwnA+cCpwHHAwc1AZQ3KqN/37QdkhFTpwIrAoupXcDV8tz7ldFdNQyHoI6r/Pco4ETZOR8ILNa2AnOU0StddknFcTETgVWBFfhNwGKgUxm9pxlAFADmAPGbrgTODqzoZwD/KLqWatHJi59xcyAw+rAHRScro89RRm8RnbGXUc0Medd4/h5l9BZl9DnAyTK3vgBDHS486yhqDleLKnFx+mYEmGQX8AXg1croVc3eDUUByqx3FfBqmWNXgMfPEN6lIr+8YyjhkHkBJrcOOEUZvUQZ/UyrnGNngZE5PaOMXgKcInMuS/OEh/46JKPEpwO3BYhN/Qo4Nw1ftxIQDdY/BrgROCNA7OvNyujV9dbfSGS1Y6O2ZcG4DjhzqICR2y1PAWfKGsrQBOBzwlM3kZVh2nmUD6Ffqow+XxndN1TAqAFKnzL6fODSko+cDZxXT5fUE1ljxJYuc7h0KbBUGb17qIHRj/hqwyZlXFHicbuB8bLznAC5WRwmbzElb9U+R0kU/wAb5fWlFcrod7qIrJnYM/AyCnxBUdt7KO0UoQWyRl+alU2c6BeQJIrTVJ2F+CckrBNrqpDOKAJYCFBDjJPVKRIL8zWJRwALkygelh+zUmNSx2LTX3wA6RI/Y21RMDJO2YnAW7FppCOAf8k8blFGP1bDUXWS//L9UODtwCuxucHPAhuBXwD3ZBheVKdMA+7ChvxdaQfwCmX0hpo7JLcdfcDoA75UBIzMWAclUXx2EsUPA38WE/ttwJvEwvsGsCWJ4l8mUfyKdL6ub3sSxSclUXyr+ALfEif3dBnrEuwxwuYkis8DxjYaIwPcWuBLnmGWEbXEeiW3iNHYfFcfy2q9hEOeKfh2xcDXHByu3cBXgcuU0TsL7o52CYEs4rkM+Ub0W+DjyugNBXfLSOBubFK4j8U1Lk1bpcYb9+ESZu4H03BIAUZNBH7q6P22AZ8EvplEcUe9cUQXtguAn3UAA+ANwC1JFKuC4vAZ4IOePGsTnu/FoJoL7F3s+eCb0kBhARlfwaaUTvEcayEwtz/nKjOHOcBHPMeIsdkk1YJKfhX2GMGHLs5iUE0XJSUBPlnou7DnGUX1xrySJjXAd5IoPqTWeMKksdgDrzL02vzb2x8oQouFF670AuE9SRRTzTxwrufErwY6i5iMQl8O4BJ0AEtqKO/062eA0QHGuSKJ4o6CpntniZdgbsqjVIccjC2WcaX/iNe5p4g5mkTxWYQrxlmQ35WZ7x8JNMZBwDuKxLzktHOF8MSVpgoGe2XkMdjKJVd6ALjfUQyEovYkil9VA/TjCJdlXwFOdXBO7xeeuNJhgsFeQI71eHN7gTuU0V0OnvRLCJfZWJHn5enIwBGTFzfSjxljogubMdPrOMY4wYCqVLtO95joHuzBjQsND8ysYQMwhmvB6Y1k8gMcaHoSxR1VWYAPIJuV0ZscwxnbAzPr3zX+9iRhEhSyetIlzrUJ2OwDCDC8Km+Aj5e53FPnhGJWpZ/g3gMBxWKaHeMaQ/PhzXFARxVblO9TB77CY6I/C8iszcrov9V4Ux+XgGEo0H9e9B9neLHCUzQeWsV2SHB2BvvLtW0w4Y3APYGYVc8PuSzQGA8qo+/1XKePk3hCVcIErnRfWf+hJG1QRv8gr78ycvyWknNM6XwHkzcEj2JfQNb6zFCY9RDwqRJM2k6x49OF2DMVX/q8Mvr+ErkAa30BmeTxw7/4zDATEPwa8BWPR+zGFsX0y6jMLnkAe6biU/T/bWBpycQMHx5NasOenLnSo76zlAXuAhYlUbwZewhVRNH/A5viv7ERozKg3CEnoL+jeE7yJwMVcvrw6IVt+JUV/LPMTDP5vFclUXyLKOGT5eUYjS2m6QaewrZY+mnKpKJvbQaUR4Ajkii+EJvZPgl7KjhMHLguEW13A0uU0SZQvrEPj8ZXkijeidsBThpq2Vg2zyp33n0wtg/KeGydxg5sf6u/pr1GApypdwBHYbMIR4gI3Ao8ooz+l+8Y/bxwxwAbHH/a3eYBBqGcu2xSgfSs2tZgR5XRW0jTsvWps1cPvEBOpXMoqKnNZ1qFhlLGZJvIatddEsTbrtE/KxVZbdgUnU7gYWV0b1mRldkpcU5kbROR9VTRNKAm8qi7kkTxdlFyLvRKHw82J4Iqkkw3Fpv+8zpsOfNo7LFAjyh1gy0A/Y4rKDnA3wu8R6ytsRKq6BWl/gRwpyj1rSGUuqQtuUYl/l2RnKjJjj88Sxn9iwA7ZB62dVORYstHgDcoox91TGabiE3tObrAGL3Ah5TR1wRY21tc4mBCm9rk7XAF5IiSu6MNmy91icNPjwQeSqL4LGX0b+qBkgFjJrbP1ciCY1SBq5Mofik2fWhXiV3iw6MnqmLnu9JRJXXGhxzBSGk4sDyJ4uPr1VjI/zsa+LEDGFlaDFxUpCYwMI+2VLE9bV3J50ArZdSR4p370iHA9xs5ndhCyzKVX0uTKJ5SYof48Ej7AnJSiYV+P4AFc2ISxefk3+CMqDoTeE2AccqUsZ3kC8gajx+2S+8QV5EVAzMDmexL8pZQ5vsXA40xPYniEzzWeQx+XSHWVHmu/bYrzcmJiCI0m3BHuC9NovjwGsw4BNu/JJS3PdsBiP/hjSP1AI+n9v56jwf4ZDpOJex597Qaf59G2HP7qR4vng9v1gM9VWAntg+6K704ieLJjpZI6Bbi4/r5W8iudmOLOIkZ/TUZyeVypNXAzqoE3HwAOQBb1uW6LUNSzyCNUY/OFd44A6KM7kmDixuw+UwuVAVOS6J4lINp+LeA4qSP2vlPIccAOWgqmJc1CtuDyzVo+6RgsPeHG7G3BLjS8dh+U0XpzoCM2iN1GXnGrMPv2LY/0O9yiGud6GlQPCYY7C3Y2YZfRelBwJwkig8oqEd+Ru1sQx/6Yf7NzXy/OtAYXdhKr4b6Q3puzcGvO9269A6TamYRyz0n/QFgQkGl14ut3ShLvUibi378kCvEWClLlyujdxSsfZkgvPCh5SmP9hbsyGUmPrm37dhObEUrja7FJh2UoY8qozvrlLRtw8bLytA9FCguyszhSk9ncHt6kUy2YGdv/MZz8mcnUTyjoAncC7wXeNhzrBuAZY3SgIAfAd8rocjfLXMtElWegX9rwKVZDAazLHoKNsj4OocxrgI+3WiMzFgHApcDFzlYPn8EPqaMXjNoZdEZRj2NvVnGh6Zg68GLFt0/iC0Xex8256oe3SUxsIscSq9RRj+LzZI8BWjUsvUJbHnzbGX0mkaWVWYOi/CvKP66MvrpfhsHyEAD3lojieK0dOxt2BSjkcKge4GbldE6++8dA33ZY9wjgXdhW2u8CJtqtFGsvz8oo/cMdmuNSg3Uh4nifY8n6uuAU4t2j3NpUON7NlH0945gjAFW4lcsm5rt7we6s2NW+hl0Jrb9kG/x5O3Y/oJ9Q7lxWYMdfRv+9fY7gDOU0XfWCn/UkvN3ClN9aRb2hrN9BozcWpZRrvnB7bXAoIHlsVCsAF+an0TxJdIWb0g3Mst0u2hLovgSynWT2y28pTAgmW6cF5RcyxVkenkMRVByIrdsv0WAC7I3veWp0uCtaAd+QvnOpNcBC4aaTsnpjGUldwbYy8TeRZ30okbdbnaJY9VZciLzgduSKB4zVHZKzpq6LQAYnRIbq1t7WC3gwK3GVjuVPWOYBaxMonhaiLuamq0vMn7GSsp3L+rD3ui2upGEKBROkGKZ6wOsdypwVxLFlyVRPLLVdktmV4xMovgycfqmBnj09UWrsgo16EqVEfbukLI0CptGencakBzs3ZLbFTMkNvUFTw88T6tS46iI/tx/oQutdaHL/iuPhuqVR7mF7b8UrD41/1KwvAwcoGvzNmOPN0NemzcXmze1T12bN1gXS/4Zm0P2V4pdLHkUNgv9JPbFiyX7AWf/1avPKfDSV6+GuJx4pVgSq57HYKwCZqRgDPblxOnX/dd3B7AI919wXy42FfyC+2pAILJhljdjI5v7Kt2KPRFtmI82qDskF4Jox7ZHuoZy91i1Eu0W8XSDMnpXMyIKTZH1OWDGYGvRZxGuwfFA0w7skfbC7OFSM0I7TVW+OUdyJvbo8h1DCJgd2GTr76Zn4M0+YBtQa0jut5qM7bt4YQuLst3YhMFlwKa0PdRA0EB3A+pWRm9QRi/Glp59hvDNlcvQdpnTOGX0Ykli6x7ICQyKv5Df9nJ/xlzsYdBhhK9F7I+exBbLrAOWp1noAyGaWgqQOsAcjO3Ediw2BjUd6fgcaMi04ni1fDZgO+NtG2wgWgKQekyQdnzDBYwJ2HLnWD6TsP0ZxwMH8ly/r25sn62t2NzgLdhOFRrbtrVTQNkpxa60ChAp/RdLBDnJ9t9abQAAAABJRU5ErkJggg=="
}const twilio = require('twilio');
module.exports = {
type: 'apiKey',
definition: () => {
return {
tokenType: 'authentication-token',
accountNameFromProfileInfo: 'accountSID',
auth: {
accountSID: {
type: 'text',
name: 'Account SID',
tooltip: 'Log into your Twilio account and find <i>API Credentials</i> on your settings page.'
},
authenticationToken: {
type: 'text',
name: 'Authentication Token',
tooltip: 'Found directly next to your Account SID.'
}
},
validate: context => {
let client = new twilio(context.accountSID, context.authenticationToken);
return client.api.accounts.list();
}
};
}
};{
"name": "appmixer.twilio",
"version": "1.0.0",
"dependencies": {
"twilio": "^3.14.0"
}
}const twilio = require('twilio');
module.exports = {
receive(context) {
let { accountSID, authenticationToken } = context.auth;
let client = twilio(accountSID, authenticationToken);
return client.incomingPhoneNumbers.list()
.then(res => {
return context.sendJson(res, 'numbers');
});
}
};{
"name": "appmixer.twilio.sms.ListFromNumbers",
"author": "David Durman <[email protected]>",
"description": "When triggered, returns a list of numbers from user.",
"private": true,
"auth": {
"service": "appmixer:twilio"
},
"inPorts": [ "in" ],
"outPorts": [ "numbers" ]
}{
"name": "appmixer.twilio.sms.ListFromNumbers",
"version": "1.0.0",
"description": "Appmixer component for twilio to get a list of phone numbers of a user.",
"main": "ListFromNumbers.js",
"author": "David Durman <[email protected]>",
"dependencies": {
"twilio": "^3.14.0"
}
}module.exports.fromNumbersToSelectArray = (numbers) => {
let transformed = [];
if (Array.isArray(numbers)) {
numbers.forEach(number => {
transformed.push({
label: number['phoneNumber'],
value: number['phoneNumber']
});
});
}
return transformed;
};Authentication to apps.

{
"type" : "routes",
"acl" : [
{
"role" : "admin",
"resource" : "flows",
"action" : [
"*"
],
"attributes" : [
"*"
]
},
{
"role" : "tester",
"resource" : "flows",
"action" : [
"*"
],
"attributes" : [
"*"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"read"
],
"attributes" : [
"*"
]
}
]
}{
"_id" : ObjectId("5f3696f502ea801d405bb112"),
"userId" : ObjectId("5f3696f102ea801d405bb10e"),
"flowId" : "c57f1d30-52fc-4a48-97d9-5d1c296064da",
"stage" : "stopped",
"name" : "Flow with metadata",
"btime" : ISODate("2020-08-14T13:51:49.900Z"),
"mtime" : ISODate("2020-09-01T14:57:43.536Z"),
"flow" : {
},
"mode" : "module",
"sharedWith" : [],
"customFields" : {
"category" : "test-example"
}
}{
"type" : "routes",
"acl" : [
{
"role" : "admin",
"resource" : "flows",
"action" : [
"*"
],
"attributes" : [
"*"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"read"
],
"attributes" : [
"* // are able to read all attributes, including custom fields
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"create"
],
"attributes" : [
"*", "!customFields" // but they're not able to create them
]
}
]
}{
"flow":{},
"name":"New flow",
"customFields": {
"category": "test-category"
}
}{
"type" : "routes",
"acl" : [
{
"role" : "admin",
"resource" : "flows",
"action" : [
"*"
],
"attributes" : [
"*"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"read"
],
"attributes" : [
"*",
"!customFields"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"create"
],
"attributes" : [
"*",
"!customFields"
]
}
]
}{
"type" : "routes",
"acl" : [
{
"role" : "admin",
"resource" : "flows",
"action" : [
"*"
],
"attributes" : [
"*"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"read",
"create"
],
"attributes" : [
"flow",
"name",
"flowId",
"stage",
"sharedWith"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"!create"
],
"attributes" : [
"customFields"
]
}
]
}{
"_id" : ObjectId("5f4e654c2c2d2f4d59ca89f9"),
"userId" : ObjectId("5f3696f102ea801d405bb10e"),
"flowId" : "46d80c28-72c3-42f1-94ba-2c161e26f041",
"stage" : "stopped",
"name" : "New flow",
"btime" : ISODate("2020-09-01T15:14:20.999Z"),
"mtime" : ISODate("2020-09-01T15:14:20.999Z"),
"flow" : {},
"mode" : "module",
"sharedWith" : [],
"customFields" : {
"visible" : "test visible",
"not-visible" : "test non visible"
}
}{
"type" : "routes",
"acl" : [
{
"role" : "admin",
"resource" : "flows",
"action" : [
"*"
],
"attributes" : [
"*"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"read"
],
"attributes" : [
"*",
"!customFields.not-visible"
]
},
{
"role" : "user",
"resource" : "flows",
"action" : [
"create"
],
"attributes" : [
"*",
"!customFields"
]
}
]
}engine:
...
environment:
- APPMIXER_API_URL=${APPMIXER_API_URL:-http://localhost:2200}
- GRIDD_URL=${APPMIXER_API_URL:-http://localhost:2200}
...APPMIXER_API_URL=https://247bb870a6f4.ngrok.io docker-compose --project-name appmixer upengine:
...
environment:
- APPMIXER_API_URL=https://247bb870a6f4.ngrok.io
- GRIDD_URL=https://247bb870a6f4.ngrok.io
...engine:
...
environment:
- GRIDD_URL=https://247bb870a6f4.ngrok.io
...
"componentType": "appmixer.slack.list.SendChannelMessage",
"auth": {
"accounts": {
"5a6e21f3b266224186ac7d03": {
"accessTokenValid": true,
"accountId": "5a6e21f3b266224186ac7d03",
"tokenId": "5a6e21f3b266224186ac7d04",
"componentAssigned": true,
"componentId": "e25dc901-f92a-46a2-8d29-2573d4ad65e5",
"scopeValid": true,
"authorizedScope": [
"channels:read",
"chat:write:user"
],
"name": "U0UFJ0MFG - client IO",
"displayName": "client IO"
}
}
}
}[
{
"accountId": "5a6e21f3b266224186ac7d03",
"name": "U0UFJ0MFG - client IO",
"displayName": null,
"service": "appmixer:slack",
"userId": "58593f07c3ee4f239dc69ff7",
"profileInfo": {
"id": "U0UFJ0MFG - client IO"
},
"icon": "data:image/png;base64,...rkJggg==",
"label": "Slack"
},
{
"accountId": "5a7313abb3a60729efe76f1e",
"name": "[email protected]",
"displayName": null,
"service": "appmixer:pipedrive",
"userId": "58593f07c3ee4f239dc69ff7",
"profileInfo": {
"name": "tomas",
"email": "[email protected]"
},
"icon": "data:image/png;base64,...rkJggg==",
"label": "Pipedrive"
}
] // filtering acme accounts and aws accounts
curl --request GET 'http://api.acme.com/accounts?filter=service:!acme:[service]&filter=service:!appmixer:aws' \
--header 'Authorization: Bearer [ACCESS_TOKEN]'{
"accountId": "5f841f3a43f477a9fa8fa4e9",
"name": "[Name of the account]",
"displayName": null,
"service": "[vendor:service]",
"userId": "5f804b96ea48ec47a8c444a7",
"profileInfo": {
},
"pre": {},
"revoked": false
}curl --request POST 'https://api.acme.com/accounts' \
--header 'Authorization: Bearer [ACCESS_TOKEN]' \
--header 'Content-Type: application/json' \
--data-raw '{
"service": "appmixer:slack",
"token": {
"accessToken": "[slack access token]",
"scope": [
"channels:write",
"groups:write",
"channels:read",
"channels:history",
"groups:read",
"groups:history",
"users:read",
"chat:write:user"
]
},
"profileInfo": {
"id" : "[Name of the account that will be used in the frontend]"
}
}'curl --request POST 'https://api.acme.com/accounts' \
--header 'Authorization: Bearer [ACCESS_TOKEN]' \
--header 'Content-Type: application/json' \
--data-raw '{
"service": "appmixer:google",
"token": {
"token": "[google access token]",
"expDate": "2021-02-04 15:34:48.833Z",
"refreshToken": "[google refresh token]",
"scope": [
"https://www.googleapis.com/auth/analytics",
"https://www.googleapis.com/auth/analytics.readonly",
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.appdata",
"https://www.googleapis.com/auth/drive.file",
"https://mail.google.com/",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.readonly",
"https://spreadsheets.google.com/feeds",
"profile",
"email"
]
}
}'curl --request POST 'https://api.acme.com/accounts' \
--header 'Authorization: Bearer [ACCESS_TOKEN]' \
--header 'Content-Type: application/json' \
--data-raw '{
"service": "appmixer:aws",
"token": {
"accessKeyId" : "[AWS access key ID]",
"secretKey" : "[AWS secret key]"
}
}'{ "5a6e21f3b266224186ac7d04": "valid" }{ "accountId": "5abcd0ddc4c335326198c1b2" }[
{
"flowId": "9251b4b6-4cdb-42ad-9431-1843e05307be",
"name": "Flow #1"
},
{
"flowId": "777d3024-43f6-4034-ac98-1cb5f320cb3a",
"name": "Flow #2"
},
{
"flowId": "9089f275-f5a5-4796-ba23-365412c5666e",
"name": "Flow #3"
}
]{ "ticket": "58593f07c3ee4f239dc69ff7:1d2a90df-b192-4a47-aaff-5a80bab66de5" }{
"authUrl": "https://slack.com/oauth/authorize?response_type=code&client_id=25316748213.218351034294&redirect_uri=http%3A%2F%2Flocalhost%3A2200%2Fauth%2Fslack%2Fcallback&state=38133t07c3ee4f369dc69ff7%3A1d2a90df-b192-4a47-aaff-5a80bab66de5&scope=channels%3Aread%2Cchat%3Awrite%3Auser"
}{
"service": "appmixer:slack"
}{ "componentId": "e25dc901-f92a-46a2-8d29-2573d4ad65e5" }{
"accountId":"5a6e21f3b266224186ac7d03",
"componentId":"e25dc901-f92a-46a2-8d29-2573d4ad65e5"
}

















[
{
"userId": "58593f07c3ee4f239dc69ff7",
"flowId": "9089f275-f5a5-4796-ba23-365412c5666e",
"stage": "stopped",
"name": "Flow #4",
"btime": "2018-03-29T19:24:08.950Z",
"mtime": "2018-04-05T12:50:15.952Z",
"sharedWith": [{
"email": "[email protected]",
"permissions": ["read", "start", "stop"]
}],
"flow": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": {
"type": "appmixer.utils.http.Uptime",
"label": "Uptime",
"source": {},
"x": 110,
"y": 90,
"config": {}
},
"43f1f63a-ecd2-42dc-a618-8c96b4acc767": {
"type": "appmixer.utils.email.SendEmail",
"label": "SendEmail",
"source": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": [
"up"
]
}
},
"x": 320,
"y": -10,
"config": {
"transform": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": {
"up": {
"type": "json2new",
"lambda": {
"from_email": "[email protected]",
"text": "Site {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.target}}} is back UP.\nDowntime: {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.downTimeText}}}\nHTTP Status Code: {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.statusCode}}}",
"subject": "Appmixer: Site UP ({{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.target}}})"
}
}
}
}
}
}
},
"416150af-b0d4-4d06-8ad1-75b17e578532": {
"type": "appmixer.utils.email.SendEmail",
"label": "SendEmail",
"source": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": [
"down"
]
}
},
"x": 320,
"y": 195,
"config": {
"transform": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": {
"down": {
"type": "json2new",
"lambda": {
"from_email": "[email protected]",
"subject": "Appmixer: Site DOWN ({{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.down.target}}})",
"text": "Site {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.down.target}}} is DOWN.\nHTTP Status Code: {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.down.statusCode}}}"
}
}
}
}
}
}
}
},
"mode": "module",
"thumbnail": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D...",
"started": "2018-04-05T12:33:15.357Z"
},
{
"userId": "58593f07c3ee4f239dc69ff7",
"flowId": "93198d48-e680-49bb-855c-58c2c11d1857",
"stage": "stopped",
"name": "Flow #5",
"btime": "2018-04-03T15:48:52.730Z",
"mtime": "2018-04-11T07:41:22.767Z",
"flow": {
"ce0742f4-4f72-4ea2-bea6-62cfaa2def86": {
"type": "appmixer.utils.email.SendEmail",
"label": "SendEmail",
"source": {
"in": {
"3d71d67f-df0b-4723-bf85-20c97f6eaff6": [
"weather"
]
}
},
"x": 485,
"y": 95,
"config": {
"transform": {
"in": {
"3d71d67f-df0b-4723-bf85-20c97f6eaff6": {
"weather": {
"type": "json2new",
"lambda": {
"from_email": "[email protected]",
"subject": "Appmixer: Current Weather",
"text": "Temperature: {{{$.3d71d67f-df0b-4723-bf85-20c97f6eaff6.weather.main.temp}}} dgC\nPressure: {{{$.3d71d67f-df0b-4723-bf85-20c97f6eaff6.weather.main.pressure}}} hPa\nHumidity: {{{$.3d71d67f-df0b-4723-bf85-20c97f6eaff6.weather.main.humidity}}}%\nCloudiness: {{{$.3d71d67f-df0b-4723-bf85-20c97f6eaff6.weather.clouds.all}}}%",
"to": ""
}
}
}
}
}
}
},
"3d71d67f-df0b-4723-bf85-20c97f6eaff6": {
"type": "appmixer.utils.weather.GetCurrentWeather",
"label": "GetCurrentWeather",
"source": {
"location": {
"b4d1ddbc-4bed-4de3-8fe1-9d9542d03cf0": [
"out"
]
}
},
"x": 290,
"y": 95,
"config": {
"transform": {
"location": {
"b4d1ddbc-4bed-4de3-8fe1-9d9542d03cf0": {
"out": {
"type": "json2new",
"lambda": {
"city": "Prague",
"units": "metric"
}
}
}
}
}
}
},
"b4d1ddbc-4bed-4de3-8fe1-9d9542d03cf0": {
"type": "appmixer.utils.controls.OnStart",
"label": "OnStart",
"source": {},
"x": 105,
"y": 95,
"config": {}
}
},
"mode": "module",
"thumbnail": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D...",
"started": "2018-04-06T12:59:29.631Z"
}
]{
"userId": "58593f07c3ee4f239dc69ff7",
"flowId": "9089f275-f5a5-4796-ba23-365412c5666e",
"stage": "stopped",
"name": "Flow #4",
"btime": "2018-03-29T19:24:08.950Z",
"mtime": "2018-04-05T12:50:15.952Z",
"flow": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": {
"type": "appmixer.utils.http.Uptime",
"label": "Uptime",
"source": {},
"x": 110,
"y": 90,
"config": {}
},
"43f1f63a-ecd2-42dc-a618-8c96b4acc767": {
"type": "appmixer.utils.email.SendEmail",
"label": "SendEmail",
"source": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": [
"up"
]
}
},
"x": 320,
"y": -10,
"config": {
"transform": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": {
"up": {
"type": "json2new",
"lambda": {
"from_email": "[email protected]",
"text": "Site {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.target}}} is back UP.\nDowntime: {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.downTimeText}}}\nHTTP Status Code: {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.statusCode}}}",
"subject": "Appmixer: Site UP ({{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.up.target}}})"
}
}
}
}
}
}
},
"416150af-b0d4-4d06-8ad1-75b17e578532": {
"type": "appmixer.utils.email.SendEmail",
"label": "SendEmail",
"source": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": [
"down"
]
}
},
"x": 320,
"y": 195,
"config": {
"transform": {
"in": {
"e15ef119-8fcb-459b-aaae-2a3f9ee41f15": {
"down": {
"type": "json2new",
"lambda": {
"from_email": "[email protected]",
"subject": "Appmixer: Site DOWN ({{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.down.target}}})",
"text": "Site {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.down.target}}} is DOWN.\nHTTP Status Code: {{{$.e15ef119-8fcb-459b-aaae-2a3f9ee41f15.down.statusCode}}}"
}
}
}
}
}
}
}
},
"mode": "module",
"thumbnail": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D...",
"started": "2018-04-05T12:33:15.357Z"
}{
"count": 29
} {
"flowId": "26544d8c-5209-44ac-9bdf-ef786924b07b"
}{
"flowId": "26544d8c-5209-44ac-9bdf-ef786924b07b",
"result": "updated"
}{
"flowId": "26544d8c-5209-44ac-9bdf-ef786924b07b"
}{
"flowId": "26544d8c-5209-44ac-9bdf-ef786924b07b"
}{
"dynamic":[],
"static":{
"channelId":[
{ "label": "my channel", "value":"CA0M22WU8" },
{ "label": "appmixer","value":"C6CMEGA9J" }
]
}
}{
'component1': {
'properties': {
// properties variables
},
'inputs': {
'in1': {
'srcComponentId1': {
'out1': {
'dynamic': [],
'static': {}
},
'out2': {
'dynamic': [],
'static': {}
},
},
'srcComponentId2': {
'out': {
'dynamic': [],
'static': {}
}
}
},
'in2': {
'srcComponentId1': {
'out1': {
'dynamic': [],
'static': {}
}
}
}
}
},
'component2': {
'properties': {
'dynamic': [],
'groups': null,
'inputs': null,
'schema': null,
'static': {}
}
// component2 has no input ports so there's no 'inputs'
}
}Appmixer command-line tool.
{
"properties": {
"schema": {
"properties": {
"interval": {
"type": "integer",
"minimum": 5,
"maximum": 35000
}
},
"required": [
"interval"
]
}
}{
"properties: {
"inspector": {
"inputs": {
"interval": {
"type": "number",
"group": "config",
"label": "Interval (in minutes, min 5, max 35000)"
}
},
"groups": {
"config": {
"label": "Configuration",
"index": 1
}
}
}
}
}{
"type": "text",
"label": "Text message."
}{
"type": "textarea",
"label": "A multi-line text message."
}{
"type": "number",
"label": "A numerical input.",
"min": 1,
"max": 10,
"step": 1
}{
"type": "multiselect",
"options": [
{ "content": "one", "value": 1 },
{ "content": "two", "value": 2 },
{ "content": "three", "value": 3 }
],
"placeholder": "-- Select something --",
"label": "Multi Select box"
}{
"type": "date-time",
"label": "Date",
"config": {
"enableTime": true
}
}{
"type": "toggle",
"label": "Toggle field"
}{
"type": "color-palette",
"label": "Color palette",
"options": [
{ "value": "green", "content": "Green" },
{ "value": "yellow", "content": "Yellow" },
{ "value": "orange", "content": "Orange" },
{ "value": "red", "content": "Red" },
{ "value": "purple", "content": "Purple" }
]
}{
"type": "select-button-group",
"label": "Select button group",
"options": [
{ "value": "line-through", "content": "<span style=\"text-decoration: line-through\">S</span>" },
{ "value": "underline", "content": "<span style=\"text-decoration: underline\">U</span>" },
{ "value": "italic", "content": "<span style=\"font-style: italic\">I</span>" },
{ "value": "bold", "content": "<span style=\"font-weight: bold\">B</span>" }
]
}{
"type": "select-button-group",
"label": "Select button group",
"multi": true,
"options": [
{ "value": "line-through", "content": "<span style=\"text-decoration: line-through\">S</span>" },
{ "value": "underline", "content": "<span style=\"text-decoration: underline\">U</span>" },
{ "value": "italic", "content": "<span style=\"font-style: italic\">I</span>" },
{ "value": "bold", "content": "<span style=\"font-weight: bold\">B</span>" }
]
}{
"type": "select-button-group",
"label": "Select button group with icons",
"multi": true,
"options": [
{ "value": "cloud", "icon": "data:image/png;base64,iVBORw0KGgoAA..." },
{ "value": "diamond", "icon": "data:image/png;base64,iVBORw0KGgoAAAA..." },
{ "value": "oval", "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUh..." },
{ "value": "line", "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." },
{ "value": "ellipse", "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEU..." }
]
}{
"type": "expression",
"label": "Filter expression",
"levels": ["OR", "AND"],
"exclusiveFields": ["myText"]
"fields": {
"myText": {
"type": "text",
"label": "Column",
"required": true,
"index": 1
},
"mySelect": {
"type": "select",
"label": "Filter action",
"variables": false,
"required": true,
"options": [
{ "content": "Equals", "value": "equals" },
{ "content": "Not Equals", "value": "notEquals" }
],
"index": 2
},
"myAnotherText": {
"label": "Filter value",
"type": "text",
"defaultValue": "My Filter",
"index": 3
}
]
}{
"OR": [
{
"AND": [
{ "myText": "My column name", "mySelect": "My filter action", "myAnotherText": "My filter value" },
{ "myText": "Another column name", "mySelect": "Another filter action", "myAnotherText": "Another filter value" }
]
},
{
"AND": [
{ "myText": "Alternative column", "mySelect": "Alternative action", "myAnotherText": "Alternative value" }
]
}
]
}"inputs": {
"fileId": {
"type": "filepicker",
"label": "Select file",
"index": 1,
"tooltip": "Pick a CSV file to import into the flow"
}
}"inputs": {
"file": {
"type": "googlepicker",
"index": 1,
"label": "File",
"placeholder": "Choose a file...",
"tooltip": "Choose a file to export."
}
}"inputs": {
"file": {
"type": "googlepicker",
"index": 1,
"label": "Folder",
"placeholder": "Choose a folder...",
"tooltip": "Choose a folder.",
"view": "FOLDERS"
}
}"input": {
"folder": {
"type": "onedrivepicker",
"index": 1,
"label": "Folder",
"placeholder": "Choose a folder...",
"tooltip": "Choose a folder to upload the file to.",
"view": "folders"
}
}"inputs": {
"field1": {
"type": "toggle",
"label": "This input controls rendering of field2",
"index": 1
},
"field2": {
"when": { "eq": { "field1": true }}
"type": "text",
"label": "This field will be only rendered if field1 is set to true",
"index": 2
}
}{
"type": "expression",
"label": "Filter expression",
"levels": ["OR", "AND"],
"fields": {
"myText": {
"type": "text",
"label": "Column",
"required": true,
"index": 1
},
"conditionalField": {
"when": { "eq": { "./myText": "Render" }}
"type": "select",
"label": "Filter action",
"variables": false,
"required": true,
"options": [
{ "content": "Equals", "value": "equals" },
{ "content": "Not Equals", "value": "notEquals" }
],
"index": 2
}
]
}{
"source": {
"url": "/component/appmixer/google/spreadsheets/ListColumns?outPort=out",
"data": {
"messages": {
"in": 1
},
"properties": {
"sheetId": "properties/sheetId",
"worksheet": "properties/worksheet"
},
"transform": "./transformers#columnsToInspector"
}
}
}{
inputs: { ... },
groups: { ... }
}module.exports.columnsToInspector = (columns) => {
let inspector = {
inputs: {},
groups: {
columns: { label: 'Columns', index: 1 }
}
};
if (Array.isArray(columns) && columns.length > 0) {
columns.forEach((column, index) => {
inspector.inputs[column[0]] = {
type: 'text',
group: 'columns',
index: index + 1
};
});
}
return inspector;
};"/component/appmixer/google/spreadsheets/ListColumns?outPort=out"{
"properties": {
"targetComponentProperty": "properties/myProperty"
}
}{
inputs: { ... },
groups: { ... }
}{
"transform": "./transformers#columnsToInspector"
}$ appmixer -h
Usage: appmixer [options] [command]
Appmixer command line interface.
Options:
-v, --version output the version number
-h, --help output usage information
Commands:
download|d Download component.
flow|f Flow commands.
init|i Initialize component.
login|l Login into Appmixer API.
logout|o Logout from Appmixer API.
pack|p Pack component into archive.
publish|pu Publish component.
remove|rm Remove component.
test|t Test component, authentication module, ...
url|u <url> Set Appmixer API url.
help [cmd] display help for [cmd]
Go to https://docs.appmixer.com/appmixer/ to find more information.$ appmixer flow -h
Usage: appmixer flow <command>
Flow commands.
Options:
-h, --help output usage information
Commands:
start|s <flowId> Start flow.
stop|t <flowId> Stop flow.
remove|r <flowId> Remove flow.
ls|l [flowId] Ls flow.
help [cmd] display help for [cmd]$ appmixer url https://api.appmixer.com$ appmixer login [email protected]
prompt: password:
Login successful.$ appmixer init example
Usage: appmixer init example <type> [path]
Options:
--vendor [vendor] Your vendor name.
--verbosity Verbosity level of the generated output log. Number in [0 - 2] range. Defaults to 1.
-h, --help output usage information
Examples:
$ appmixer init example no-auth
Environment Variables:
AM_GENERATOR_COMPONENT_PATH Components base directory. [path] argument overrides this variable if used.$ appmixer init example no-auth
Created directory structure ./appmixer/myservice/mymodule/MyComponent
Creating component appmixer.myservice.mymodule.MyComponent
Created component manifest file ./appmixer/myservice/mymodule/MyComponent/component.json.
Created component package file ./appmixer/myservice/mymodule/MyComponent/package.json.
Created component behaviour file ./appmixer/myservice/mymodule/MyComponent/MyComponent.js.
Created service manifest file ./appmixer/myservice/service.json.
Created quota module file ./appmixer/myservice/quota.js.
Example successfully generated.
my-components
└── appmixer
└── myservice
├── mymodule
│ └── MyComponent
│ ├── MyComponent.js
│ ├── component.json
│ └── package.json
├── quota.js
└── service.json
You can now use appmixer pack appmixer/myservice && appmixer publish commands to upload it.$ appmixer pack appmixer/myservice
Packing component directory: /Users/daviddurman/Projects/appmixer/my-components/appmixer/myservice
Files found in /Users/daviddurman/Projects/appmixer/my-components/appmixer/myservice
- mymodule
- quota.js
- service.json
You are in a directory with service.json file.
I'm going to create directory structure based on name in your service.json file.
3866 total bytes
appmixer.myservice.zip$ appmixer publish appmixer.myservice.zip
Publishing archive: /Users/daviddurman/Projects/appmixer/my-components/appmixer.myservice.zip
Published.$ appmixer component ls
appmixer.actimo.contacts.CreateContact
appmixer.actimo.contacts.DeleteContact
appmixer.actimo.contacts.GetContact
appmixer.actimo.contacts.GetContacts
appmixer.actimo.contacts.UpdateContact
appmixer.actimo.groups.GetGroups
appmixer.actimo.messages.SendMessage
appmixer.apify.crawlers.Crawl
appmixer.asana.projects.CreateProject
appmixer.asana.projects.NewProject
appmixer.asana.tasks.CreateStory
appmixer.asana.tasks.CreateSubtask
appmixer.asana.tasks.CreateTask
appmixer.asana.tasks.NewComment
...appmixer remove appmixer.myservice.mymodule.MyComponentappmixer remove appmixer.myservice$ appmixer test -h
Usage: appmixer test <command>
Dev tools for testing your files.
Options:
-h, --help output usage information
Commands:
dump|d <moduleName> Get stored authentication data from previous commands.
auth|a <authModuleFile> Authenticate service.
component|c <componentFile> Test component.
help [cmd] display help for [cmd]$ appmixer test component -h
Usage: appmixer test component [options] [componentDir]
Options:
-f, --transform [transform] specify transformer
-i, --input [input] input test message object (default: [])
-m, --mime [mime] mime type, application/json by default
-p, --properties [properties] component properties (JSON format)
-s, --no-state do not show component's state
-t, --tickPeriod [tickPeriod] tick period (in ms), default is 10000 ms
-h, --help output usage information
Examples:
Following example will send input message { "to": "[email protected]" } to component's input port 'in'.
You always have to specify to which input port you want to send message.
$ appmixer test component [path-to-your-component-directory] -i '{ "in": { "to": "[email protected]" } }'
This is how to specify transformer function from transformer file.
$ appmixer test component [path-to-component] -i '{}' -f './transformers#channelsToSelectArray'
How to set properties and tick period:
$ appmixer t c [path-to-component] -p '{ "channelId: "123XYZ" }' -t 2000
You can send more than one message:
$ appmixer t c [path-to-component] -i '{ "in": { "to": "[email protected]" }}' -i '{ "in": { "to": "[email protected]" }}'
You can run appmixer command in your component's directory:
$ appmixer test c
Directory has to contain component.json file and component's source code file.
If you're developing component that needs authentication, use 'appmixer test auth' before.
If you're developing Oauth2 component you might need to refresh access token before calling this command.
Use 'appmixer test auth refresh' for such purposes.
Some of the feature (context.store, context.componentStaticCall, ... will work only if you are logged in into Appmixer.
If you want to test them in your component, call appmixer login first.
For more information, checkout our documentation at:
https://docs.appmixer.com/appmixer/component-definition/authentication
https://docs.appmixer.com/appmixer/appmixer-trial/custom-component-helloappmixer$ appmixer test component appmixer/myservice/mymodule/MyComponent -i '{ "in": { "sourceData": "foo" } }'
Testing /Users/daviddurman/Projects/appmixer/my-components/appmixer/myservice/mymodule/MyComponent
Validating properties.
Test server is listening on 2300
Starting component.
Calling receive method with input message:
in:
-
properties:
correlationId: null
gridInstanceId: null
contentType: application/json
contentEncoding: utf8
sender: null
destination: null
correlationInPort: null
componentHeaders:
signal: false
content:
sourceData: foo
scope:
{"name":"component","hostname":"MacBook-Pro.local","pid":26397,"level":30,"msg":"{\"properties\":{\"correlationId\":\"8f2ce09e-3f6d-48dd-81bd-e80189f70bb4\",\"gridInstanceId\":null,\"contentType\":\"application/json\",\"contentEncoding\":\"utf8\",\"sender\":{\"componentId\":\"70eb49e9-88df-4d0e-9549-d4e4f0f755c0\",\"type\":\"appmixer.myservice.mymodule.MyComponent\",\"outputPort\":\"out\"},\"destination\":null,\"correlationInPort\":null,\"componentHeaders\":{},\"signal\":false},\"content\":{\"sourceData\":\"foo\"},\"scope\":{\"_walkthrough\":[{\"targetId\":\"70eb49e9-88df-4d0e-9549-d4e4f0f755c0\",\"links\":[]}]}} { componentId: '70eb49e9-88df-4d0e-9549-d4e4f0f755c0',\n flowId: 'c5d05118-13b5-4ec8-a8fe-2e6ef51fdd52',\n userId: '5da735715abc4a671dfb592f',\n componentType: 'appmixer.myservice.mymodule.MyComponent',\n type: 'data',\n portType: 'out',\n port: 'out',\n inputMessages: { in: [ [Object] ] },\n annotatedMsg: { sourceData: 'foo' } }","time":"2019-10-16T15:21:22.169Z","v":0}
Component sent a message to its output port: out
{ sourceData: 'foo' }
Component's receive method finished in: 45 ms.
Return value from receive method:
undefined
Component's state at the end:
State is empty, component did not store anything into state.
Stopping component.
Destroying component.module.exports = {
receive(context) {
myBadError
context.sendJson(context.messages.in.content, 'out');
}
}$ appmixer test component appmixer/myservice/mymodule/MyComponent -i '{ "in": { "sourceData": "foo" } }'
Testing /Users/daviddurman/Projects/appmixer/my-components/appmixer/myservice/mymodule/MyComponent
Validating properties.
Test server is listening on 2300
Starting component.
Calling receive method with input message:
in:
-
properties:
correlationId: null
gridInstanceId: null
contentType: application/json
contentEncoding: utf8
sender: null
destination: null
correlationInPort: null
componentHeaders:
signal: false
content:
sourceData: foo
scope:
[ERROR]: myBadError is not defined$ appmixer download -h
Usage: appmixer download selector
Options:
-o, --out [dir] Where you want save it.
-h, --help output usage information
Examples:
Download all files for SendEmail component:
$ appmixer download vendor.google.gmail.SendEmail
Download only package.json file for SendEmail component:
$ appmixer download vendor.google.gmail.SendEmail/package.json
Download main SendEmail.js file only:
$ appmixer download vendor.google.gmail.SendEmail/SendEmail.js
Download all gmail components:
$ appmixer download vendor.google.gmail
Download auth.js file for gmail:
$ appmixer download vendor.google.gmail/auth.js
Download quota.js file for gmail:
$ appmixer download vendor.google.gmail/quota.js$ appmixer download appmixer.myservice
$ ls
appmixer.zip
$ unzip appmixer.zip
$ tree appmixer/
appmixer
└── myservice
├── mymodule
│ └── MyComponent
│ ├── MyComponent.compiled.js
│ ├── MyComponent.js
│ ├── component.json
│ ├── package-lock.json
│ ├── package.json
│ └── sourcemap-register.js
└── service.json
3 directories, 7 files$ appmixer flow ls
[Get Current Weather] : [a5769b32-8835-44ad-82e1-ece2874ea3e3] : [stopped]
[Uptime Monitor] : [5b5fd3a0-0ef2-4fc5-9a60-164f5e44c660] : [stopped]
[Daily Rainy Day Alert] : [ec1103e5-c66c-41c4-9223-029d2b328f5c] : [stopped]$ appmixer flow ls a5769b32-8835-44ad-82e1-ece2874ea3e3
Flow: a5769b32-8835-44ad-82e1-ece2874ea3e3
Stage: stopped$ appmixer flow ls a5769b32-8835-44ad-82e1-ece2874ea3e3 --descriptor
{
"5ba2740c-929b-4599-b09e-fc8f8d5dac82": {
"type": "appmixer.utils.controls.OnStart",
"label": "OnStart",
"source": {},
"config": {},
"x": 88,
"y": 110
},
"0f366972-08fe-4cd4-80c0-227cfb6db54b": {
"type": "appmixer.utils.weather.GetCurrentWeather",
"label": "GetCurrentWeather",
"source": {
"location": {
"5ba2740c-929b-4599-b09e-fc8f8d5dac82": [
"out"
]
}
},
"config": {
"transform": {
"location": {
"5ba2740c-929b-4599-b09e-fc8f8d5dac82": {
"out": {
"type": "json2new",
"lambda": {
"city": "Prague",
"units": "metric"
}
}
}
}
}
},
"x": 286,
"y": 110
},
"ab6a22f8-916d-4aab-a5e3-2abedc03917c": {
"type": "appmixer.utils.email.SendEmail",
"label": "SendEmail",
"source": {
"in": {
"0f366972-08fe-4cd4-80c0-227cfb6db54b": [
"weather"
]
}
},
"config": {
"transform": {
"in": {
"0f366972-08fe-4cd4-80c0-227cfb6db54b": {
"weather": {
"type": "json2new",
"lambda": {
"from_email": "[email protected]",
"subject": "Appmixer: Current Weather",
"text": "Temperature: {{{$.0f366972-08fe-4cd4-80c0-227cfb6db54b.weather.main.temp}}} dgC\nPressure: {{{$.0f366972-08fe-4cd4-80c0-227cfb6db54b.weather.main.pressure}}} hPa\nHumidity: {{{$.0f366972-08fe-4cd4-80c0-227cfb6db54b.weather.main.humidity}}}%\nCloudiness: {{{$.0f366972-08fe-4cd4-80c0-227cfb6db54b.weather.clouds.all}}}%",
"to": ""
}
}
}
}
}
},
"x": 484,
"y": 110
}
} $ appmixer flow start a5769b32-8835-44ad-82e1-ece2874ea3e3
Flow a5769b32-8835-44ad-82e1-ece2874ea3e3 successfully started.
$ appmixer flow stop a5769b32-8835-44ad-82e1-ece2874ea3e3
Flow a5769b32-8835-44ad-82e1-ece2874ea3e3 successfully stopped.$ appmixer flow remove 2058a1ee-9c19-4e94-bd7a-0da7f9bed973
Flow 2058a1ee-9c19-4e94-bd7a-0da7f9bed973 successfully removed.$ appmixer modifiers get$ appmixer modifiers publish file-with-your-modifiers.json$ appmixer modifiers delete
// or any library you want to perform API requests
const request = require('request-promise');
module.exports = {
// there will be some other properties based on authenication mechanism
// defined prior requestProfileInfo
definition: {
// requestProfileInfo can be defined as a function. In this case, you
// can do whatever you need in here and return object with user's
// profile information (in promise)
requestProfileInfo: async context => {
// curl https://mydomain.freshdesk.com/api/v2/agents/me \
// -u myApiKey:X'
return request({
method: 'GET',
url: `https://${context.domain}.acme.com/api/v2/agents/me`,
auth: {
user: context.apiKey // 'context' will be explained later
},
json: true
});
},
// or you can specify it as an object. In this case, the object follows
// the 'request' javascript library options.
requestProfileInfo: {
method: 'GET',
url: 'https://acme.com/get-some-records/app_id={{appId}}',
headers: {
'Authorization': 'Basic {{apiKey}}' // {{apiKey}} explained later
}
},
// the last way is to specify just the URI. In this case Appmixer
// will perform GET request to that URI.
requestProfileInfo: 'https://acme.com/get-profile-info?apiKey={{apiKey}}'
}
}
module.exports = {
type: 'apiKey',
definition: {
tokenType: 'authentication-token',
auth: {
domain: {
type: 'text',
name: 'Domain',
tooltip: 'Your Freshdesk subdomain - e.g. if the domain is <i>https://example.freshdesk.com</i> just type <b>example</b> inside this field'
},
apiKey: {
type: 'text',
name: 'API Key',
tooltip: 'Log into your Freshdesk account and find <i>Your API Key</i> in Profile settings page.'
}
},
accountNameFromProfileInfo: 'contact.email',
requestProfileInfo: async context => {
// curl https://mydomain.freshdesk.com/api/v2/agents/me \
// -u myApiKey:X'
return request({
method: 'GET',
url: `https://${context.domain}.freshdesk.com/api/v2/agents/me`,
auth: {
user: context.apiKey,
password: 'X'
},
json: true
});
},
validate: async context => {
// curl https://mydomain.freshdesk.com/api/v2/agents/me \
// -u myApiKey:X'
const credentials = `${context.apiKey}:X`;
const encoded = (new Buffer(credentials)).toString('base64');
await request({
method: 'GET',
url: `https://${context.domain}.freshdesk.com/api/v2/agents/me`,
headers: {
'Authorization': `Basic ${encoded}`
}
});
// if the request doesn't fail, return true (exception will be captured in caller)
return true;
}
}
};{
contact: {
email: '[email protected]',
name: 'Appmixer example',
// More properties here...
}
// There can be more properties here as well...
}'use strict';
module.exports = {
type: 'apiKey',
definition: {
tokenType: 'apiKey',
accountNameFromProfileInfo: 'appId',
auth: {
appId: {
type: 'text',
name: ' APP ID',
tooltip: 'Log into your account and find Api key.'
},
apiKey: {
type: 'text',
name: 'REST API Key',
tooltip: 'Found directly next to your App ID.'
}
},
// In the validate request we neet the appId and apiKey specified by user.
// All properties defined in the previous 'auth' object will be
// available in the validate call. Just use {{whatever-key-from-auth-object}}
// anywhere in the next object. Appmixer will replace these with the
// correct values.
validate: {
method: 'GET',
url: 'https://acme.com/get-some-records/app_id={{appId}}',
headers: {
'Authorization': 'Basic {{apiKey}}'
}
}
}
};
'use strict';
const OAuth = require('oauth').OAuth;
const Promise = require('bluebird');
module.exports = {
type: 'oauth',
// In this example, 'definition' property is defined as a function.
definition: context => {
let trelloOauth = Promise.promisifyAll(new OAuth(
'https://trello.com/1/OAuthGetRequestToken',
'https://trello.com/1/OAuthGetAccessToken',
context.consumerKey,
context.consumerSecret,
'1.0',
context.callbackUrl,
'HMAC-SHA1'
), { multiArgs: true });
return {
accountNameFromProfileInfo: 'id',
authUrl: context => {
return 'https://trello.com/1/OAuthAuthorizeToken' +
'?oauth_token=' + context.requestToken +
'&name=AppMixer' +
'&scope=read,write,account' +
'&expiration=never';
},
requestRequestToken: () => {
return trelloOauth.getOAuthRequestTokenAsync()
.then(result => {
return {
requestToken: result[0],
requestTokenSecret: result[1]
};
});
},
requestAccessToken: context => {
return trelloOauth.getOAuthAccessTokenAsync(
context.requestToken,
context.requestTokenSecret,
context.oauthVerifier
).then(result => {
return {
accessToken: result[0],
accessTokenSecret: result[1]
};
});
},
requestProfileInfo: context => {
return trelloOauth.getProtectedResourceAsync(
'https://api.trello.com/1/members/me',
'GET',
context.accessToken,
context.accessTokenSecret
).then(result => {
if (result[1].statusCode !== 200) {
throw new Error(result[1].statusMessage);
}
result = JSON.parse(result[0]);
// get rid of limits for now
// may and will contain keys with dots - mongo doesn't like it
delete result.limits;
return result;
});
},
validateAccessToken: context => {
return trelloOauth.getProtectedResourceAsync(
'https://api.trello.com/1/tokens/' + context.accessToken,
'GET',
context.accessToken,
context.accessTokenSecret
).then(result => {
if (result[1].statusCode === 401) {
throw new context.InvalidTokenError(result[1].statusMessage);
}
if (result[1].statusCode !== 200) {
throw new Error(result[1].statusMessage);
}
result = JSON.parse(result[0]);
if (result['dateExpires'] === null) {
return;
}
throw new context.InvalidTokenError('Invalid token.');
});
}
};
}
};
'use strict';
const request = require('request-promise');
module.exports = {
type: 'oauth2',
// function definition is used in this case because of the 'profileInfo'
// property. It is set in the 'requestAccessToken' function and then
// later returned in 'requestProfileInfo' function.
definition: () => {
let profileInfo;
return {
accountNameFromProfileInfo: context => {
return context.profileInfo['email'] || context.profileInfo['id'].toString();
},
authUrl: 'https://app.asana.com/-/oauth_authorize',
requestAccessToken: context => {
// don't put params into post body, won't work, have to be in query
let tokenUrl = 'https://app.asana.com/-/oauth_token?' +
'grant_type=authorization_code&code=' + context.authorizationCode +
'&redirect_uri=' + context.callbackUrl +
'&client_id=' + context.clientId +
'&client_secret=' + context.clientSecret;
return request({
method: 'POST',
url: tokenUrl,
json: true
}).then(result => {
profileInfo = result['data'];
let newDate = new Date();
newDate.setTime(newDate.getTime() + (result['expires_in'] * 1000));
return {
accessToken: result['access_token'],
refreshToken: result['refresh_token'],
accessTokenExpDate: newDate
};
});
},
requestProfileInfo: () => {
return profileInfo;
},
refreshAccessToken: context => {
// don't put params into post body, won't work, have to be in query
let tokenUrl = 'https://app.asana.com/-/oauth_token?' +
'grant_type=refresh_token&refresh_token=' + context.refreshToken +
'&redirect_uri=' + context.callbackUrl +
'&client_id=' + context.clientId +
'&client_secret=' + context.clientSecret;
return request({
method: 'POST',
url: tokenUrl,
json: true
}).then(result => {
profileInfo = result['data'];
let newDate = new Date();
newDate.setTime(newDate.getTime() + (result['expires_in'] * 1000));
return {
accessToken: result['access_token'],
accessTokenExpDate: newDate
};
});
},
validateAccessToken: {
method: 'GET',
url: 'https://app.asana.com/api/1.0/users/me',
auth: {
bearer: '{{accessToken}}'
}
}
};
}
};
authUrl: 'https://www.dropbox.com/oauth2/authorize''use strict';
module.exports = {
type: 'oauth2',
definition: {
accountNameFromProfileInfo: 'email',
authUrl: 'https://www.dropbox.com/oauth2/authorize',
requestAccessToken: 'https://api.dropbox.com/oauth2/token',
requestProfileInfo: {
'method': 'POST',
'uri': 'https://api.dropboxapi.com/2/users/get_current_account',
'headers': {
'authorization': 'Bearer {{accessToken}}'
}
}
}
};
'use strict';
const TENANT = 'common';
module.exports = {
type: 'oauth2',
definition: {
scope: ['offline_access', 'user.read'],
scopeDelimiter: ' ',
authUrl: `https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/authorize`,
requestAccessToken: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
refreshAccessToken: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
accountNameFromProfileInfo: 'displayName',
emailFromProfileInfo: 'mail',
requestProfileInfo: 'https://graph.microsoft.com/v1.0/me',
validateAccessToken: {
method: 'GET',
url: 'https://graph.microsoft.com/v1.0/me',
auth: {
bearer: '{{accessToken}}'
}
}
}
};



















const twilio = require('twilio');
module.exports = {
receive(context) {
let { fromNumber } = context.properties;
let { accountSID, authenticationToken } = context.auth;
let message = context.messages.message.content;
let client = new twilio(accountSID, authenticationToken);
return client.message.create({
body: message.body,
to: message.to,
from: fromNumber
});
}
};
{
receive(context) {
const smsContent = context.messages.message.content;
}
}Humidity: {{{$.ec8cd99f-0ad3-4bca-9efc-ebea5be6b596.weather.main.humidity}}}context.messages.message.content === 'Humidity: 75'const twilio = require('twilio');
module.exports = {
type: 'apiKey',
definition() {
return {
tokenType: 'authentication-token',
accountNameFromProfileInfo: 'accountSID',
auth: {
accountSID: {
type: 'text',
name: 'Account SID',
tooltip: 'Log into your Twilio account and find <i>API Credentials</i> on your settings page.'
},
authenticationToken: {
type: 'text',
name: 'Authentication Token',
tooltip: 'Found directly next to your Account SID.'
}
},
validate: context => {
let client = new twilio(context.accountSID, context.authenticationToken);
return client.api.accounts.list();
}
};
}
};{
receive(context) {
let { accountSID, authenticationToken } = context.auth;
}
}module.exports = {
receive(context) {
// prepare the qs
// the 'apiKey' value set in the Backoffice will be available
// at context.auth.apiKey (and context.config.apiKey, context.config
// is an alias to context.auth)
return weather.get('/weather', qs, context.auth.apiKey)
.then(body => {
// process results
});
}
};
{
"properties": {
"schema": {
"properties": {
"fromNumber": { "type": "string" }
}
},
"inspector": {
...
}
}
}{
receive(context) {
const fromNumber = context.properties.fromNumber;
}
}{
async receive(context) {
// Emit a message only once per day for this component instance.
const day = (new Date).getDay();
const state = context.state;
if (!state[day]) {
state[day] = true;
await context.saveState(state);
return context.sendJson({ tick: true }, 'out');
}
}
}module.exports = {
async start(context) {
// register webhook in the slack plugin
return context.service.stateAddToSet(
context.properties.channelId,
{
componentId: context.componentId,
flowId: context.flowId
}
);
},
async stop(context) {
return context.service.stateRemoveFromSet(
context.properties.channelId,
{
componentId: context.componentId,
flowId: context.flowId
}
);
}
}{
receive(context) {
// getAttachment() is some function that retrieves a file from an API
return getAttachment(context.auth, context.messages.attachment.content.id)
.then((file) => {
return context.saveFile(file.name, file.mimeType, Buffer.from(file.data, 'base64'));
})
.then((result) => {
return context.sendJson({ fileId: result.fileId }, 'file');
});
}
}{
"filename": "testFile",
"contentType": "text",
"length": 7,
"chunkSize": 261120,
"uploadDate": "2021-01-22T12:20:29.227Z",
"metadata": {
"userId": "5f804b96ea48ec47a8c444a7",
"fileId": "fd0e9149-3249-4d42-b519-bfd9ab6773c5"
},
"md5": "9a0364b9e99bb480dd25e1f0284c8555",
"fileId": "fd0e9149-3249-4d42-b519-bfd9ab6773c5"
}{
receive(context) {
return context.loadFile(context.messages.file.content.fileId)
.then((fileContent) => {
// uploadFileToAPI() is some function that uploads a file to an API
return uploadFileToAPI(context.auth, content.messages.file.content.fileName, fileContent);
});
}
}module.exports = {
async receive(context) {
if (context.messages.webhook) {
// Webhook URL received data.
await context.sendJson(context.messages.webhook.content.data, 'myOutPort');
// Send response to the webhook HTTP call.
// Note: you can also skip sending response immediately and send it
// in other connected components in the flow.
// If context.response() is not called, the engine waits for the first component
// that sends the response (in the same "session", i.e. the same "message flow").
return context.response('<myresponse></myresponse>', 200, { 'Content-Type': 'text/xml' });
}
// Otherwise, normal input port received data.
const input = context.messages.myInPort.content;
// The webhook URL. Do something with it (send to your API, send to other connected,
// components, send to your backend, ...)
const url = context.getWebhookUrl();
}
};
module.exports = {
receive(context) {
if (context.messages.timeout) {
// Timeout message.
return context.sendJson(context.messages.timeout.content, 'out');
} else {
// Normal input message.
return context.setTimeout(context.messages.in.content, 5 * 60 * 1000);
}
}
};const task = await context.callAppmixer({
endPoint: '/people-task/tasks',
method: 'POST',
body: {
title: 'My Task',
description: 'My Example Task',
requester: '[email protected]',
approver: '[email protected]',
decisionBy: (function() {
const tomorrow = new Date;
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow.toISOString();
})()
}
});module.exports = {
async receive(context) {
return context.stopFlow();
}
};[
{
"variables": {
"dynamic": [
{
"label": "Column A",
"value": "columnA",
"componentId": "2c724dff-a04b-43ed-9787-c9a891a721cc",
"port": "out"
},
{
"label": "Column B",
"value": "columnB",
"componentId": "2c724dff-a04b-43ed-9787-c9a891a721cc",
"port": "out"
}
],
"static": {}
},
"sourceComponentId": "2c724dff-a04b-43ed-9787-c9a891a721cc",
"outPort": "out",
"inPort": "in"
},
{
"variables": {
"dynamic": [
{
"label": "Column C",
"value": "columnC",
"componentId": "71eb1f40-ce29-4963-8922-102739bafee4",
"port": "out"
},
{
"label": "Column D",
"value": "columnD",
"componentId": "71eb1f40-ce29-4963-8922-102739bafee4",
"port": "out"
}
],
"static": {}
},
"sourceComponentId": "71eb1f40-ce29-4963-8922-102739bafee4",
"outPort": "out",
"inPort": "in"
}
]{
receive(context) {
return context.loadVariables()
.then(data => {
const newSchema = data.reduce((acc, item) => {
return acc.concat(item.variables.dynamic.map(o => ({ label: o.label, value: o. value })));
}, []);
context.sendJson(newSchema, 'leftJoin');
context.sendJson(newSchema, 'innerJoin');
context.sendJson(newSchema, 'rightJoin');
});
}
}{
async start(context) {
await context.log({ test: 'my test log' });
return context.sendJson({ started: (new Date()).toISOString() }, 'out');
}
} async receive(context) {
let lock = null;
try {
lock = await context.lock(context.flowId, {
ttl: 30000,
maxRetryCount: 1
});
let { callCount = 0, messages = [] } = await context.loadState();
messages.push(context.messages.in.originalContent);
if (++callCount === context.messages.in.content.callCount) {
await context.sendJson(messages || [], 'out');
await context.saveState({ callCount });
return;
}
await context.saveState({ callCount, messages });
} finally {
if (lock) {
await lock.unlock();
}
}
}
/**
* Example of context.CancelError
*/
module.exports = {
async receive(context) {
let data = context.messages.in.content;
try {
// In this component is trying to create a record in a 3rd party
// system.
const resp = await someAPI.createSomething(data);
await context.sendJson(resp, 'out');
} catch (err) {
// And there might be a unique constraint, let's say an email
// address. And the 3rd party API will return an error with a
// message saying that this record cannot be created.
if (err.message === 'duplicate record') {
// In this case, we can tell Appmixer to cancel the message.
// Because next attempt would fail again with the same result.
throw new context.CancelError(err.message);
}
// In case of any other error, rethrow the exception. Appmixer will
// then try to process it again.
throw err;
}
}
};





