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...
Loading...
Loading...
{ "label": "Create Tweet" }{
"icon": "data:image/svg+xml;base64,PD94bWwgdmV..."
}{ "name": "appmixer.twitter.statuses.CreateTweet" }{
"author": "David Durman <[email protected]>"
}{
"description": "This action gets the current weather conditions for a location."
}{
"marker": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL..."
}{
"authConfig": {
"service": "appmixer:deepai"
}
}{
"quota": {
"manager": "pipedrive",
"resources": "requests",
"scope": {
"userId": "{{userId}}"
}
}
}{
"auth": {
"service": "appmixer:google",
"scope": [
"https://mail.google.com/",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send"
]
}
}{
"firePatterns": ['*', 1]
}{
"firePatterns": [1, 1]
}{
"name": "appmixer.twilio.sms.SendSMS",
"version": "1.0.0",
"private": true,
"main": "SendSMS.js",
"author": "David Durman <[email protected]>",
"dependencies": {
"twilio": "^2.11.0"
}
}options.changeActionCallback replaced by a new event: component:update-type










Appmixer SKD is a toolkit to integrate workflow automation engine and embed white-labeled user interface widgets into your products. Gain a whole new set of comprehensive features with ease.
filters: [
{ property: 'stage', label: 'Running flows', value: 'running' },
{ property: 'stage', label: 'Stopped flows', value: 'stopped' },
{ property: 'sharedWith', label: 'My flows', value: 'myFlows' },
{ property: 'sharedWith', label: 'Shared with others', value: 'sharedWithOthers' },
{ property: 'sharedWith', label: 'Shared with me', value: 'sharedWithMe' }
],{
"firePatterns": [
['*', 1],
[1, 0]
]
}{
"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 -XPOST "https://api.[acme].appmixer.cloud/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 "https://api.[acme].appmixer.cloud/flows/count" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkyNjA5LCJleHAiOjE1NTUwODQ2MDl9.9jVcqY0qo9Q_1GeK9Fg14v7OrdpWvzmqnv4jDMZfqnI"{"count":5}{
"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
}
}
}
}
]
}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]}}}{
...
"inPorts": [
{
"name": "in",
"schema": {
"type": "object"
},
"source": {
// The ListColumns component can return an array of columns in a
// Worksheet.
"url": "/component/appmixer/google/spreadsheets/ListColumns?outPort=out",
"data": {
// The ListColumns component needs two properties in order
// to get the list of columns, the Spreasheet Id and the
// Worksheet Id. Both will be taken from properties of the
// CreateRow component (the caller).
"properties": {
// Appmixer will replace 'properties/sheetId' with
// the actual value before making the call
"sheetId": "properties/sheetId",
"worksheetId": "properties/worksheetId"
},
// A transformer function 'columnsToInspector' from the
// ListColumns.js will be executed in order to transform a list
// of columns to the Appmixer Inspector.
"transform": "./ListColumns#columnsToInspector"
}
}
}
]
}{
"variablesPipeline": {
"scopeDepth": 1,
"rawValue": true
}
}{
"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" }
]
}
]
}{
"outPorts": [
{
"name": "out",
"source": {
// We will call another component to construct the output port
// options, in this case, the GetRows component
"url": "/component/appmixer/google/spreadsheets/GetRows?outPort=out",
// Every Appmixer component can have 'properties' and input ports,
// the 'data' sections is used to create the input data object
// for the component
"data": {
// in this particular case, the GetRows component has an
// optional property called 'generateOutputPortOptions', we
// will pass that property with the value 'true'. The GetRows
// component will use this property to change its return value
// and instead of returning rows from Worksheet, it will
// return the 'options' array.
"properties": {
"generateOutputPortOptions": true
},
// the GetRows component expects the Spreadsheet ID and
// Worksheet ID as part of the message at its input port
// called 'in'. The UpdatedRow component is a trigger, it
// does not have an input port, but it has the same options like
// 'allAtOnce', 'withHeaders', ... and since it does not have
// an input port, it has these options defined in the
// 'properties' section. The next 'messages' section is used
// to construct an input port object for the GetRows component.
// It copies the user defined properties from the UpdatedRow.
// Appmixer will replace these with the actual values before
// calling the GetRows component.
"messages": {
"in/sheetId": "properties/sheetId",
"in/worksheetId": "properties/worksheetId",
"in/allAtOnce": "properties/allAtOnce",
"in/withHeaders": "properties/withHeaders",
"in/rowFormat": "properties/rowFormat"
}
}
}
}
]
}
{
"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]}}}"
}
}
}
}
}
}
}
}const insightsDashboard = appmixer.ui.InsightsDashboard(config)
insightsDashboard.set(key, value)
insightsDashboard.get(key)const connectors = appmixer.ui.Connectors(config)
connectors.set(key, value)
connectors.get(key)const storage = appmixer.ui.Storage(config)
storage.set(key, value)
storage.get(key)const peopleTasks = appmixer.ui.PeopleTasks(config)
peopleTasks.set(key, value)
peopleTasks.get(key)

wget https://my.YOURTENANT.appmixer.cloud/appmixer/package/appmixer.es.jsimport { Appmixer } from 'appmixer.es.js'
const appmixer = new Appmixer(/* ... */){ "ok": true }

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 });/* Create "Designer". */
var designer = appmixer.ui.Designer({
el: '#your-designer',
options: designerOptions(),
api: {
// extending the updateFlow request
updateFlow(flowId, update) {
// at this place you can call your own API every time the flow
// gets updated
// carefully catch errors, timeouts ... so calling your
// external API does not affect the Designer behaviour
console.log('Calling your own API.');
console.log(JSON.parse(JSON.stringify(update)));
// in order to update the flow in Appmixer, call the Flow API
return this._request({
url: `${this.get('baseUrl')}/flows/${flowId}`,
method: 'PUT',
data: update
});
}
}
});config.el ...
config.el ...
{
"key": "myConfigKey",
"value": "My Custom Value"
}{ "ok": true }insightsDashboard.state(name, value)insightsDashboard.on(event, handler)insightsDashboard.on('chart:clone', chartId => {/* ... */})insightsDashboard.on('chart:remove', chartId => {/* ... */})insightsDashboard.on('chart:open', chartId => {/* ... */})const insightsDashboard = appmixer.ui.InsightsDashboard({
el: '#insights-dashboard'
})
insightsDashboard.open()import { Designer, FlowManager } from 'appmixer.es.js'
appmixer.ui('Designer', Designer)
appmixer.ui('FlowManager', FlowManager)
const designer = appmixer.ui.Designer(/* ... */)
const flowManager = appmixer.ui.FlowManager(/* ... */){ "ok": true }connectors.state(name, value)const connectors = appmixer.ui.Connectors({
el: '#connectors'
})
connectors.open()storage.state(name, value)const storage = appmixer.ui.Storage({
el: '#storage'
})
storage.open()peopleTasks.state(name, value)const peopleTasks = appmixer.ui.PeopleTasks({
el: '#people-tasks'
})
peopleTasks.open(){
"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"
}
}
}const insightsChartEditor = appmixer.ui.InsightsChartEditor(config)
insightsChartEditor.set(key, value)
insightsChartEditor.get(key)insightsChartEditor.state(name, value)insightsChartEditor.on(event, handler)insightsChartEditor.on('close', () => {/* ... */})const insightsChartEditor = appmixer.ui.InsightsChartEditor({
el: '#insights-chart-editor'
})
insightsChartEditor.open()const wizard = appmixer.ui.Wizard(config)
wizard.set(key, value)
wizard.get(key)wizard.state(name, value)wizard.on(event, handler)wizard.on('flow:open', flowId => {/* ... */})wizard.on('cancel', () => {/* ... */})const wizard = appmixer.ui.Wizard({
el: '#wizard',
flowId: 'your-integration-id'
})
wizard.on('flow:start', async flowId => {
await appmixer.api.startFlow(flowId)
wizard.close()
})
wizard.open(){
"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;
}
}curl "https://api.appmixer.com/variables/93198d48-e680-49bb-855c-58c2c11d1857?srcComponentId=ba09820f-db59-4739-b22d-414826842495&srcComponentOut=trigger&tgtComponentId=e25dc901-f92a-46a2-8d29-2573d4ad65e5&tgtComponentIn=message" -H "Authorization: Bearer [ACCESS_TOKEN]"{
// Response
}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.
{
"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...."
} // 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": [
{
...
}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
... engine:
...
environment:
- APP_NAME=ACME
...var 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.htmlconst accounts = appmixer.ui.Accounts(config)
accounts.set(key, value)
accounts.get(key)









config.elappmixer.set('strings', {
ui: {
flowManager: {
search: 'Search flows',
header: {
buttonCreateFlow: 'Create new Flow'
}
}
}
});wget https://my.appmixer.com/appmixer/package/strings-en.jsonappmixer.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', {
ui: {
flowManager: {
pagination: '{{range}} of {{total}} flow|{{range}} of {{total}} flows'
}
}
});accounts.state(name, value)accounts.on(event, handler)accounts.on('flow:open', flowId => {/* ... */})const accounts = appmixer.ui.Accounts({
el: '#accounts'
})
accounts.open()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'
}]
};{
"foo": false,
"bar": { "counter": 1 }
}// set properties by key or path
widget.state('foo', true)
widget.state('bar', { counter: 2 })
widget.state('bar/counter', 3)
// get properties by key or path
widget.state('foo') // true
widget.state('bar') // { counter: 3 }
widget.state('bar/counter') // 3
// get the entire state
widget.state() // { foo: true, bar: { counter: 3 } }
// reset the state to defaults
widget.reset() // { foo: false, bar: { counter: 1 } }const appmixer = new Appmixer(/* ... */)
const widget = appmixer.ui.FlowManager(config)widget.open()widget.close()widget.reload()widget.reset()widget.state(path, value) // setter
widget.state(path) // getterwidget.set(key, value)widget.get(key, value)widget.on(name, handler)widget.off(name){
"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"
}config.baseUrl{
"user": {
"
Routes for setting ACL. Used in Backoffice.
Appmixer allows you to save global configuration values for your service. If a component contains either an auth section or authConfig section, values for specified service will be injected.
{
"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"
}const appmixer = new Appmixer({/* [name]: value */})
appmixer.set(name, value)
appmixer.get(name)appmixer.ui('Widget', {/* ... */})
appmixer.ui.Widget({/* ... */})appmixer.set(key, value)appmixer.get(key, value)appmixer.registerCustomComponentShape(name, shape)appmixer.registerInspectorField(type, Field, options)<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://my.YOURTENANT.appmixer.cloud/appmixer/appmixer.js"></script>
</head>
<body>
<div id="your-widget"></div>
<script async type="module">
const appmixer = new Appmixer({ baseUrl: BASE_URL })
const auth = await appmixer.api.signupUser(USERNAME, PASSWORD)
appmixer.set('accessToken', auth.token)
appmixer.ui.FlowManager({ el: '#your-widget' }).open()
</script>
</body>
</html>{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkyNjA5LCJleHAiOjE1NTUwODQ2MDl9.9jVcqY0qo9Q_1GeK9Fg14v7OrdpWvzmqnv4jDMZfqnI"
}{
"id": "58593f07c3ee4f239dc69ff7",
"username": "[email protected]",
"isActive": true,
"email": "[email protected]",
"scope": [
"user"
],
"plan": "beta"
}curl "https://api.appmixer.com/apps" -H "Authorization: Bearer [ACCESS_TOKEN]" <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'),
}),
],
};const integrations = appmixer.ui.Integrations(config)
integrations.set(key, value)
integrations.get(key)

[
{
"role": "admin",
"resource": "*",
"action": [
"*"
],
"attributes": [
"non-private"
]
},
{
"role": "user",
"resource": "*",
"action": [
"*"
],
"attributes": [
"non-private"
]
},
{
"role": "tester",
"resource": "*",
"action": [
"*"
],
"attributes": [
"non-private"
]
}
][
"routes",
"components"
][
{
"serviceId": "appmixer:google",
"clientID": "my-global-client-id",
"clientSecret": "my-global-client-secret"
},
{
"serviceId": "appmixer:evernote",
"sandbox": true,
}
]
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"['*', 'flows']['*', 'read', '!read', 'create', '!create', 'update', '!update', 'delete', '!delete']{
"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"
}{}appmixer.ui.Integrations({
/* ... */
options: {
customFilter: {
// displaying Integrations with a certain customField
'customFields.category': 'healthcare',
// that are shared with someone
'sharedWith': '![]'
// and in this example, that are shared throught the 'domain'
// options with a 'testdomain.com'
'sharedWith.domain': 'testdomain.com'
// or we could filter only Integrations that are shared with
// scope 'user'
// 'sharedWith.scope': 'user',
}
}
}integrations.state(name, value)integrations.on(event, handler)integrations.on('integration:create', integrationId => {/* ... */})integrations.on('integration:edit', integrationId => {/* ... */})integrations.on('integration:remove', integrationId => {/* ... */})integrations.on('integration:start', integrationId => {/* ... */})integrations.on('integration:stop', integrationId => {/* ... */})const integrations = appmixer.ui.Integrations({
el: '#integrations'
})
integrations.open()
integrations.on('integration:create', async templateId => {
// Create integration flow as a clone of the template. Disconnect
// accounts because they might not be shared with the end user.
const integrationId = await appmixer.api.cloneFlow(templateId, { connectAccounts: false })
await appmixer.api.updateFlow(integrationId, { templateId })
const wizard = appmixer.ui.Wizard({
el: '#your-wizard-div',
flowId: integrationId,
});
wizard.open()
integrations.reload()
})
integrations.on('integration:edit', function(integrationId) {
var wizard = appmixer.ui.Wizard({
el: '#wizard',
flowId: integrationId
});
wizard.open()
});
integrations.open(){"count":5}[
{
"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",







{
"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...
}{}{}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 })const insightsLogs = appmixer.ui.InsightsLogs(config)
insightsLogs.set(key, value)
insightsLogs.get(key)










insightsLogs.state(name, value)const insightsLogs = appmixer.ui.InsightsLogs({
el: '#insights-logs'
})
insightsLogs.open(){
"categories": {
"object": {
"label": "Object",
"index": 1
},
"list": {
"label": "List",
"index": 2
},
...
},
"modifiers": {
"g_stringify": {
"name": "stringify",
<script>
var BASE_URL = '<your-base-url>';
var USERNAME = '<your-username>';
var PASSWORD = '<your-password>';


Build, edit and inspect individual flows in a comprehensive editor.
config.el ...config.flowIdconfig.componentIdconfig.shareTypesconfig.sharePermissionsconfig.options.showHeaderconfig.options.menuconfig.options.toolbarloadererrorflow:startflow:stopflow:shareflow:renameflow:export-svgflow:export-pngflow:printflow:wizard-buildercomponent:addcomponent:opencomponent:closecomponent:renamecomponent:update-typenavigate:validationLet's start with a simple user interface to browse and manage your flows.
<script src="https://my.YOURTENANT.appmixer.cloud/appmixer/appmixer.js"></script>







Appmixer lets you manage the components' inspector fields through the manifest or the strings object.
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;
}{
"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\""
}# Download the Appmixer SDK:
$ wget http://my.[acme].appmixer.cloud/appmixer/appmixer.js
# Download the demo page:
$ wget http://my.[acme].appmixer.cloud/appmixer/demo.html
# Download the example theme object:
$ wget http://my.[acme].appmixer.cloud/appmixer/theme.js
$ open demo.htmlappmixer.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);const designer = appmixer.ui.Designer(config)
designer.set(key, value)
designer.get(key)<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://my.YOURTENANT.appmixer.cloud/appmixer/appmixer.js"></script>
</head>
<body>
<div id="flow-manager"></div>
<div id="designer"></div>
<script async type="module">
const appmixer = new Appmixer()
appmixer.set('baseUrl', BASE_URL)
try {
const auth = await appmixer.api.signupUser(USERNAME, PASSWORD)
appmixer.set('accessToken', auth.token)
} catch (error) {
alert(error)
}
const flowManager = appmixer.ui.FlowManager({ el: '#flow-manager' })
const designer = appmixer.ui.Designer({ el: '#designer' })
flowManager.on('flow:open', flowId => {
flowManager.close()
designer.set('flowId', flowId)
designer.open()
})
flowManager.open()
</script>
</body>
</html>const appmixer = new Appmixer()
















appmixer.ui.Designer({
/* ... */
options: {
menu: [
{ event: 'flow:rename', label: 'Rename', icon: 'data:image/svg+xml;base64,...' },
{ event: 'flow:share', label: 'Share', icon: 'https://www.example.com/images/image.jpg' },
{ event: 'flow:wizard-builder', label: 'Wizard' },
{ event: 'flow:export-svg', label: 'Export SVG' },
{ event: 'flow:export-png', label: 'Export PNG' },
{ event: 'flow:print', label: 'Print' }
]
}
}const designer = appmixer.ui.Designer({
/* ... */
options: {
toolbar: [
['undo', 'redo'],
['zoom-to-fit', 'zoom-in', 'zoom-out'],
['logs'],
[{
tooltip: 'Reload',
widget: {
template: (
`<div @click="onClick" style="border: solid 1px gray; border-radius: 3px;">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px">
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
</svg>
</div>`
),
methods: {
onClick() {
designer.reload()
}
}
}
}]
]
}
}designer.state(name, value)designer.on(event, handler)designer.on('flow:start', flow => {/* ... */})designer.on('flow:stop', flow => {/* ... */})designer.on('flow:share', flow => {/* ... */})designer.on('flow:rename', flow => {/* ... */})designer.on('flow:export-svg', flow => {/* ... */})designer.on('flow:export-png', flow => {/* ... */})designer.on('flow:print', flow => {/* ... */})designer.on('flow:wizard-builder', flow => {/* ... */})designer.on('component:add', ({ data, next }) => {/* ... */})designer.on('component:open', ({ data, next }) => {/* ... */})designer.on('component:close', ({ data, next }) => {/* ... */})designer.on('component:rename', ({ data, next }) => {/* ... */})designer.on('component:update-type', ({ data, next }) => {/* ... */})designer.on('navigate:validation', (flowId) => {/* ... */})const designer = appmixer.ui.Designer({
el: '#designer',
options: {
menu: [
{ event: 'flow:rename', label: 'Rename' },
{ event: 'flow:share', label: 'Share' },
{ event: 'flow:wizard-builder', label: 'Wizard' },
{ event: 'flow:export-svg', label: 'Export SVG' },
{ event: 'flow:export-png', label: 'Export PNG' },
{ event: 'flow:print', label: 'Print' }
],
toolbar: [
['undo', 'redo'],
['zoom-to-fit', 'zoom-in', 'zoom-out'],
['logs']
]
}
})
const flowId = await appmixer.api.createFlow('New flow')
designer.set('flowId', flowId)
designer.open()appmixer.set('baseUrl', BASE_URL)const auth = await appmixer.api.signupUser(USERNAME, PASSWORD)
appmixer.set('accessToken', auth.token)const auth = await appmixer.api.authenticateUser(USERNAME, PASSWORD)<div id="flow-manager"></div>
<div id="designer"></div>const flowManager = appmixer.ui.FlowManager({ el: '#flow-manager' })
const designer = appmixer.ui.Designer({ el: '#designer' })flowManager.open()
flowManager.on('flow:open', flowId => {
flowManager.close()
designer.set('flowId', flowId)
designer.open()
})
Access Data Stores (built-in key-value store).
Browse and manipulate flows that are accessible to the current user.
config.el ...config.optionsconfig.options.menuconfig.options.shareTypesconfig.options.sharePermissionsconfig.options.filtersconfig.options.customFilterconfig.options.sortingloadererrorlayoutqueryflow:openflow:createflow:startflow:stopflow:cloneflow:shareflow:renameflow:remove
const appmixer = new Appmixer({
theme: {
variables: {
font: {
family: “sans-serif”
},
colors: {
neutral00: “orange”
}
}
}
});const flowManager = appmixer.ui.FlowManager({
el: "#my-flow-manager",
theme: {
variables: {
colors: {
neutral00: 'purple'
}
}
}
});// change the theme of all widgets
appmixer.set('theme', {
variables: {
font: {
family: 'serif'
},
}
})
// or/and change the theme of a single widget
widget.set('theme', {
variables: {
colors: {
neutral00: 'green'
}
}
});appmixer.set('theme', {
variables: {
font: {
family: '\'SF Pro Text\', \'Helvetica Neue\', \'Helvetica\', \'Arial\', sans-serif',
familyMono: '\'SF Mono\', \'ui-monospace\', Menlo, monospace',
weightRegular: 400,
weightMedium: 500,
weightSemibold: 600,
size: 14
},
colors: {
base: '#FFFFFF',
neutral: '#131314',
focus: '#3688EB',
error: '#DE3123',
warning: '#B56C09',
success: '#09CD96',
modifier: '#C558CF',
highlighter: '#FFA500'
},
shadows: {
backdrop: 'rgba(0 0 0 / 6%)',
popover: '0 3px 9px rgba(0 0 0 / 12%)',
icon: '0 1px 3px rgb(0 0 0 / 6%)'
},
corners: {
radiusSmall: '3px',
radiusMedium: '6px',
radiusLarge: '9px'
},
dividers: {
regular: '1px',
medium: '2px',
semibold: '3px',
bold: '6px',
extrabold: '9px'
}
}
})appmixer.set('theme', {
ui: {
shapes: {
action: "action",
trigger: "trigger",
selection: "selection"
}
}
})appmixer.set('theme', {
ui: {
charts: {
legendFontSize: 12px,
legendFontFamily: "sans-serif",
legendFontColor: "black",
tickFontSize: "black",
tickFontFamily: "monospaced",
tickFontColor: "black",
gridColor: "lightgray",
colorway: [
'#1452cc',
'#8a47c4',
'#c636b0',
'#ef2c94',
'#ff3d74',
'#ff5e52',
'#ff8230',
'#ffa600'
]
}
}
})appmixer.ui.FlowManager({
el: '#app',
theme: {
ui: {
'#FlowManager': {
background: 'lightblue',
'#header': {
padding: '0 0 24px 0',
'#buttonCreateFlow': {
color: 'yellow',
'@hovered': {
color: 'white'
}
}
}
}
}
}
});wget https://my.appmixer.com/appmixer/package/theme-light.json
wget https://my.appmixer.com/appmixer/package/theme-dark.json{
"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ů."
}appmixer.set('strings', {
ui: {
designer: {
stencil: {
groups: {
applications: 'Connectors',
utilities: 'Tools'
}
}
}
}
});







[{
"name": "My Store 1",
"storeId": "5c6fc9932ff3ff000747ead4"
}, {
"name": "My Store 2",
"storeId": "2a3fc9512bb3fca23747lai2"
}]{
"name": "My Store 1",
"storeId": "5c6fc9932ff3ff000747ead4"
}




{
chartId: '5defb3901f17d98d974fbb00'
}{
"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"
}]{
"storeId": "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"
}{
"key": "New Key",
"value": "New Value"
"createdAt": "2021-09-01T11:34:00.258+0000",
"updatedAt": "2021-09-01T11:34:00.258+0000",
}{
"deletedCount": 1
}const flowManager = appmixer.ui.FlowManager(config)
flowManager.set(key, value)
flowManager.get(key)appmixer.ui.FlowManager({
/* ... */
options: {
menu: [
{ event: 'flow:open', label: 'Open', icon: 'data:image/svg+xml;base64,...' },
{ event: 'flow:rename', label: 'Rename', icon: 'https://www.example.com/images/image.jpg' },
{ event: 'flow:insights', label: 'Insights' },
{ event: 'flow:clone', label: 'Clone' },
{ event: 'flow:share', label: 'Share' },
{ event: 'flow:remove', label: 'Remove' },
{ event: 'my-custom-event', label: 'Custom Event' }
]
}
}appmixer.ui.FlowManager({
/* ... */
options: {
filters: [
{ property: 'stage', value: 'running', label: 'Running flows' },
{ property: 'stage', value: 'stopped', label: 'Stopped flows' },
{ property: 'sharedWith', value: 'myFlows', label: 'My flows' },
{ property: 'sharedWith', value: 'sharedWithOthers', label: 'Shared with others' },
{ property: 'sharedWith', value: 'sharedWithMe', label: 'Shared with me' }
]
}
}appmixer.ui.FlowManager({
/* ... */
options: {
customFilter: {
stage: 'running'
}
}
}appmixer.ui.FlowManager({
/* ... */
options: {
customFilter: {
'customFields.category': 'healthcare',
'customFields.template': true
}
}
}appmixer.ui.FlowManager({
/* ... */
options: {
sorting: [
{ label: 'Last Modified', property: 'mtime', value: -1 },
{ label: 'Last Created', property: 'btime', value: -1 }
]
}
}flowManager.state(name, value)flowManager.on(event, handler)flowManager.on('flow:open', flowId => {/* ... */})flowManager.on('flow:create', () => {/* ... */})flowManager.on('flow:start', flowId => {/* ... */})flowManager.on('flow:stop', flowId => {/* ... */})flowManager.on('flow:clone', flowId => {/* ... */})flowManager.on('flow:share', flowId => {/* ... */})flowManager.on('flow:rename', flowId => {/* ... */})flowManager.on('flow:remove', flowId => {/* ... */})appmixer.ui.FlowManager({
/* ... */
options: {
menu: [{ event: 'flow:share', label: 'Share' }],
// specify custom types and scopes
shareTypes: [
{ value: 'email', label: 'Email', placeholder: 'Enter an email' },
{ value: 'scope', label: 'Scope', placeholder: 'Enter a scope' },
{ value: 'domain', label: 'Domain', placeholder: 'Enter a domain' }
],
// override default permissions
sharePermissions: [
{ label: 'Read', value: 'read' },
{ label: 'Start', value: 'start' },
{ label: 'Stop', value: 'stop' }
]
}
}// create a new widget
const flowManager = appmixer.ui.FlowManager({
el: '#flow-manager',
options: {
menu: [
{ event: 'flow:open', label: 'Open' },
{ event: 'custom-event', label: 'Custom' },
{ event: 'flow:rename', label: 'Rename' },
{ event: 'flow:insights', label: 'Insights' },
{ event: 'flow:clone', label: 'Clone' },
{ event: 'flow:share', label: 'Share' },
{ event: 'flow:remove', label: 'Remove' }
],
filters: [
{ property: 'stage', value: 'running', label: 'Running flows' },
{ property: 'stage', value: 'stopped', label: 'Stopped flows' },
{ property: 'sharedWith', value: 'myFlows', label: 'My flows' },
{ property: 'sharedWith', value: 'sharedWithOthers', label: 'Shared with others' },
{ property: 'sharedWith', value: 'sharedWithMe', label: 'Shared with me' }
],
sorting: [
{ label: 'Last Modified', property: 'mtime', value: -1 },
{ label: 'Last Created', property: 'btime', value: -1 }
]
}
})
// change default layout
flowManager.state('layout', 'list')
// override a built-in event
flowManager.on('flow:create', () => {
flowManager.state('error', 'Creating a new flow overridden by a custom event handler.')
})
// load flow details with a custom event
flowManager.on('custom-event', async flowId => {
try {
flowManager.state('loader', true)
const flow = await appmixer.api.getFlow(flowId)
alert(`Flow ${flow.name} has ${Object.keys(flow.flow).length} component(s).`)
} catch (error) {
flowManager.state('error', 'Loading flow failed.')
} finally {
flowManager.state('loader', false)
}
})
// open the widget
flowManager.open(){
"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":[]
}
}
}
}
}{
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"
}curl "https://api.appmixer.com/logs?from=0&size=30&sort=@timestamp:desc&query=@timestamp:[2019-03-04+TO+2019-03-08]&flowId=9c4673d7-a550-45a2-91c1-ad057fac0385" -H "Authorization: Bearer [ACCESS_TOKEN]"curl "https://api.appmixer.com/logs?from=0&size=30&sort=@timestamp:desc&query=@timestamp:[2019-03-04+TO+2019-03-08]" -H "Authorization: Bearer [ACCESS_TOKEN]"{
"buckets": [
{
The Appmixer SDK uses this API module internally to connect to the REST API.
config.baseUrlconfig.accessTokenapi.authenticateUserapi.signupUserapi.createFlowapi.deleteFlowapi.getFlowapi.getFlowsapi.getFlowsCountapi.updateFlowapi.startFlowapi.stopFlowapi.cloneFlowapi.getUserapi.getStoresapi.getStoreapi.getStoreRecordsCountapi.getStoreRecordsapi.createStoreapi.deleteStoreapi.renameStoreapi.createStoreItemapi.deleteStoreItemsapi.createAccountapi.getAccountsapi.getComponentAccountsapi.getAccountFlowsapi.setAccountNameapi.getLogsapi.getLogapi.getPeopleTasksapi.getPeopleTasksCountapi.getPeopleTaskapi.approveTaskapi.rejectTaskapi.getChartsapi.getChartapi.deleteChartapi.getFlowAuthenticationerrorAPI for users
{
"_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"
]
} 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{
"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)"
},
"type": "component",
"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"
},
"type": "component",
"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
}{
"err": {
"message": "Ran out of quota for appmixer:utils:weather",
"error":429,
"data":429,
"name":"QuotaError",
"stack":"QuotaError: Ran out of quota for appmixer:utils:weather\\n at Request._callback (/Driver.js:67:31)\"
},
"type": "quota",
"flowId": "b7db896e-0e5c-4ee1-9cb3-1099c9f43b62",
"flowName": "Daily Rainy Day Alert",
"userId": "60ef4fd533d6143ba09c400f",
"componentId": "13ad74e3-3354-463c-bf30-ea6e0c157ad8",
"componentType": "appmixer.utils.weather.GetCurrentWeather",
"inputMessages": {
"location": [
{
"properties": {
"correlationId": '4dc33f4c-dbd0-4e97-ab05-381d785851f3',
"gridInstanceId": null,
...
}
]
}
}// WEBHOOK_USER_FORGOT_PASSWORD = 'http://hosted-url.com/events/forgot-password';
{
'code': 'unique code generated for identifying forgot password request',
'email': 'email address of user requesting for forgot password',
'userId': 'USER id of user',
'created': 'Date when a user requested for forgot password',
'link': 'Link to access forgot password page on frontend'
}




{
"user": {
"id": "5c88c7cc04a917256c726c3d",
"username":"[email protected]",
"isActive": false,
"email": "[email protected]",
"plan":"free"
},
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkyNjA5LCJleHAiOjE1NTUwODQ2MDl9.9jVcqY0qo9Q_1GeK9Fg14v7OrdpWvzmqnv4jDMZfqnI"
}const appmixer = new Appmixer(config)
appmixer.api.set(key, value)
appmixer.api.get(key)await appmixer.api.authenticateUser(username, password)await appmixer.api.signupUser(username, password)await appmixer.api.createFlow(name, [descriptor], [properties])await appmixer.api.deleteFlow(flowId)await appmixer.api.getFlow(flowId)await appmixer.api.getFlows(query){
limit: 20,
offset: 0,
pattern: "slack",
projection: "-flow,-thumbnail",
sort: "mtime:-1",
sharedWithPermission: "read",
filter: "userId:423jfdsalfjl4234fdsa"
}await appmixer.api.getFlowsCount(query)await appmixer.api.updateFlow(flowId, update)await appmixer.api.startFlow(flowId)await appmixer.api.stopFlow(flowId)await appmixer.api.cloneFlow(flowId)await appmixer.api.getUser()await appmixer.api.getStores()await appmixer.api.getStore(storeId)await appmixer.api.getStoreRecordsCount(query)await appmixer.api.getStoreRecords(query){
limit: 30,
offset: 0,
pattern: "foo",
sort: "updatedAt:-1",
storeId: “5c6d643f4849f447eba55c1d"
}await appmixer.api.createStore(name)await appmixer.api.deleteStore(storeId)await appmixer.api.renameStore(storeId, newName)await appmixer.api.createStoreItem(storeId, key, value)await appmixer.api.deleteStoreItems(items)await appmixer.api.createAccount(params, data)await appmixer.api.getAccounts(filter)await appmixer.api.getComponentAccounts(componentType, componentId)await appmixer.api.getAccountFlows(accountId)await appmixer.api.setAccountName(accountId, newName)await appmixer.api.getLogs(query){
from: 0,
size: 30,
sort: "@timestamp:desc",
query: "@timestamp:[2018-01-01 TO 2018-01-01]"
}{
from: 0,
size: 30,
sort: "@timestamp:desc",
query: "@timestamp:[2018-01-01 TO 2018-01-01] AND +flowId:FLOW_ID"
}await appmixer.api.getLog(logId, index)await appmixer.api.getPeopleTasks(query)await appmixer.api.getPeopleTasksCount(query)await appmixer.api.getPeopleTask(id)await appmixer.api.approveTask(id, [params])await appmixer.api.rejectTask(id, [params])await appmixer.api.getCharts()await appmixer.api.getChart(chartId)await appmixer.api.deleteChart(chartId)await appmixer.api.getFlowAuthentication(flowId)appmixer.api.on(event, handler)appmixer.api.on('error', error => {
if (error.code === 401) {
/* A request failed because the current access token is invalid ... */
}
}{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjODhjN2NjMDRhOTE3MjU2YzcyNmMzZCIsInNjb3BlIjpbInVzZXIiXSwiaWF0IjoxNTUyNDkyNjA5LCJleHAiOjE1NTUwODQ2MDl9.9jVcqY0qo9Q_1GeK9Fg14v7OrdpWvzmqnv4jDMZfqnI"
}{
"id": "58593f07c3ee4f239dc69ff7",
"username": "[email protected]",
"isActive": true,
"email": "[email protected]",
"scope": [
"user"
],
"plan": "beta"
}{
"ticket": "830639e3-c53a-42d6-ad43-0276674236b4"
}{
"status": "in-progress | completed | failed | cancelled",
"stepsDone": 4, // can be used to display a progress bar
"stepsTotal": 10 // can be used to display a progress bar
}[
{
"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...
}
]{}{
"appmixer.asana": {
"
# 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');
});
}
};[
{
"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'"
}
]
}


[{
"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",
"title": "Example title",
"id": "5da9ed9ff29cd51c5fa27380"
}]{
"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,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"
}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.
"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"
}
{
"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"
}[
{
"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"
}{
"cloneId": "cloned-flow-id"
}{
"flowId": "26544d8c-5209-44ac-9bdf-ef786924b07b"
}{
// Response
}{
// Response
}{
// Response
}{
// Response
}{
// Response
}{
// Response
}















var appmixer = new Appmixer({
componentShapes: {
action: myCustomComponentShape,
trigger: myCustomComponentShape,
myUniqueShape: myCustomComponentShape
},
});{
shape: "myUniqueShape"
}var customComponentShape = {
attributes: {
size: {
height: 72,
width: 72
},
markup: [
{
tagName: 'rect',
selector: 'body'
},
{
tagName: 'text',
selector: 'label'
},
{
tagName: 'image',
selector: 'icon'
},
{
tagName: 'g',
selector: 'element-halo',
children: [
{
tagName: 'image',
selector: 'element-halo-copy',
children: [
{
tagName: 'title',
selector: 'element-halo-copy-tooltip'
}
]
},
{
tagName: 'image',
selector: 'element-halo-cut',
children: [
{
tagName: 'title',
selector: 'element-halo-cut-tooltip'
}
]
},
{
tagName: 'image',
selector: 'element-halo-remove',
children: [
{
tagName: 'title',
selector: 'element-halo-remove-tooltip'
}
]
}
]
}
],
attrs: {
body: {
fill: 'white',
stroke: 'black',
strokeWidth: 1,
refWidth: 1,
refHeight: 1
},
icon: {
ref: 'body',
refX: 0.5,
refY: 0.5,
xAlignment: 'middle',
yAlignment: 'middle',
width: 24,
height: 24,
clipPath: 'url(#icon-clip)'
},
label: {
ref: 'body',
textAnchor: 'middle',
refX: 0.5,
refY: '100%',
refY2: 12,
xAlignment: 'middle',
fontFamily: 'sans-serif',
fontWeight: 'bold',
fontSize: 14,
fill: 'black'
},
'element-halo': {
display: 'none',
ref: 'body',
width: 68,
height: 44,
refX: 0.5,
refY: 0,
refY2: -24,
xAlignment: 'middle'
},
'element-halo-copy': {
event: 'element-copy',
x: 0,
y: 0,
width: 20,
height: 20,
xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTUgMnYyaDRWMkg1em02IDBoMWEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJIMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmgxYTIgMiAwIDAgMSAyLTJoNGEyIDIgMCAwIDEgMiAyem0wIDJhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJIMnYxNGgxMFY0aC0xek00IDhoNmExIDEgMCAwIDEgMCAySDRhMSAxIDAgMSAxIDAtMnptMCA1aDZhMSAxIDAgMCAxIDAgMkg0YTEgMSAwIDAgMSAwLTJ6Ij48L3BhdGg+PC9zdmc+',
cursor: 'pointer'
},
'element-halo-cut': {
event: 'element-cut',
x: 24,
y: 0,
width: 20,
height: 20,
cursor: 'pointer',
xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yLjUgLTIuNSAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiIgY2xhc3M9Imljb25fX2ljb24iPjxwYXRoIGQ9Ik05LjEwNyAxMC41NTRMNy4zNjMgMTIuNjNhMy45OTMgMy45OTMgMCAwIDEtLjE5MiA0Ljg5IDQuMDA0IDQuMDA0IDAgMCAxLTUuNjM1LjQ5MiAzLjk5MSAzLjk5MSAwIDAgMS0uNDkzLTUuNjI4IDQuMDA1IDQuMDA1IDAgMCAxIDQuNzg4LTEuMDM4TDcuODAxIDkgMi40MTMgMi41ODdhLjk5OC45OTggMCAwIDEgLjEyMy0xLjQwNyAxIDEgMCAwIDEgMS40MDkuMTIzbDUuMTYyIDYuMTQ0IDUuMTYxLTYuMTQ0YTEgMSAwIDAgMSAxLjQxLS4xMjMuOTk4Ljk5OCAwIDAgMSAuMTIzIDEuNDA3TDEwLjQxMiA5bDEuOTcgMi4zNDVhNC4wMDUgNC4wMDUgMCAwIDEgNC43ODggMS4wMzggMy45OTEgMy45OTEgMCAwIDEtLjQ5MyA1LjYyOCA0LjAwNCA0LjAwNCAwIDAgMS01LjYzNS0uNDkyIDMuOTkzIDMuOTkzIDAgMCAxLS4xOTItNC44OWwtMS43NDMtMi4wNzV6bS02LjI4NSA1LjkyN2EyIDIgMCAwIDAgMi41NzEtMy4wNiAyLjAwMiAyLjAwMiAwIDAgMC0yLjgxOC4yNDZjLS43MS44NDUtLjYgMi4xMDUuMjQ3IDIuODE0em0xMi41NyAwYTEuOTk2IDEuOTk2IDAgMCAwIC4yNDYtMi44MTQgMi4wMDIgMi4wMDIgMCAwIDAtMi44MTctLjI0NiAxLjk5NiAxLjk5NiAwIDAgMC0uMjQ3IDIuODE0Yy43MS44NDUgMS45NzIuOTU1IDIuODE4LjI0NnoiPjwvcGF0aD48L3N2Zz4'
},
'element-halo-remove': {
event: 'element-remove',
x: 48,
y: 0,
width: 20,
height: 20,
cursor: 'pointer',
xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zIC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTYgMlYxYTEgMSAwIDAgMSAxLTFoNGExIDEgMCAwIDEgMSAxdjFoNGEyIDIgMCAwIDEgMiAydjFhMiAyIDAgMCAxLTIgMmgtLjEzM2wtLjY4IDEwLjJhMyAzIDAgMCAxLTIuOTkzIDIuOEg1LjgyNmEzIDMgMCAwIDEtMi45OTMtMi43OTZMMi4xMzcgN0gyYTIgMiAwIDAgMS0yLTJWNGEyIDIgMCAwIDEgMi0yaDR6bTEwIDJIMnYxaDE0VjR6TTQuMTQxIDdsLjY4NyAxMC4wNjhhMSAxIDAgMCAwIC45OTguOTMyaDYuMzY4YTEgMSAwIDAgMCAuOTk4LS45MzRMMTMuODYyIDdoLTkuNzJ6TTcgOGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6bTQgMGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6Ij48L3BhdGg+PC9zdmc+'
},
'element-halo-copy-tooltip': {
fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
fontSize: 13
},
'element-halo-cut-tooltip': {
fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
fontSize: 13
},
'element-halo-remove-tooltip': {
fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
fontSize: 13
}
}
},
ports: {
attributes: {
in: {
position: {
name: 'left'
},
attrs: {
'.port-body': {
r: 5,
strokeWidth: 2,
stroke: 'black',
fill: 'white'
},
'connection-port-label': {
fill: 'black',
fontFamily: 'sans-serif',
fontSize: 12
}
},
label: {
position: {
name: 'left',
args: {
x: -8,
y: 14
}
},
markup: [
{
tagName: 'text',
selector: 'connection-port-label'
}
]
}
},
out: {
position: {
name: 'right'
},
attrs: {
'.port-body': {
r: 5,
strokeWidth: 2,
stroke: 'black',
fill: 'white'
},
'connection-port-label': {
fill: 'black',
fontFamily: 'sans-serif',
fontSize: 12
}
},
label: {
position: {
name: 'right',
args: {
x: 8,
y: 14
}
},
markup: [
{
tagName: 'text',
selector: 'connection-port-label'
}
]
}
}
}
},
link: {
attributes: {
router: {
name: 'metro'
},
connector: {
name: 'rounded',
args: {
radius: 8
}
},
markup: [
{
tagName: 'path',
selector: 'wrapper'
},
{
tagName: 'path',
selector: 'line'
}
],
tools: [
{
z: 100,
distance: '50%',
event: 'link-remove',
markup: [
{
tagName: 'circle',
selector: 'button',
attributes: {
cx: -0.5,
cy: -0.5,
r: 10,
fill: config.colors.bodyStrokeActive,
cursor: 'pointer'
}
},
{
tagName: 'path',
selector: 'icon',
attributes: {
pointerEvents: 'none',
transform: 'translate(-6.4 -7)',
d: 'M8,1.333h3.333A.667.667,0,0,1,12,2V3.333A.667.667,0,0,1,11.333,4H.667A.667.667,0,0,1,0,3.333V2a.667.667,0,0,1,.667-.667H4V.667A.667.667,0,0,1,4.667,0H7.333A.667.667,0,0,1,8,.667Zm2.533,4-.409,6.133a2,2,0,0,1-2,1.867H3.884a2,2,0,0,1-2-1.864L1.47,5.333ZM4.667,6A.667.667,0,0,0,4,6.667v4.667a.667.667,0,0,0,1.333,0V6.667A.667.667,0,0,0,4.667,6ZM7.333,6a.667.667,0,0,0-.667.667v4.667a.667.667,0,0,0,1.333,0V6.667A.667.667,0,0,0,7.333,6Z',
fill: 'white'
}
}
]
}
],
attrs: {
line: {
pointerEvents: 'none',
connection: true,
stroke: 'black',
strokeWidth: 1.5,
strokeDasharray: '4 4',
fill: 'transparent'
},
wrapper: {
connection: true,
cursor: 'pointer',
stroke: 'transparent',
strokeWidth: 24,
fill: 'transparent'
}
}
}
},
states: {
'@active': {
attributes: {
attrs: {
body: {
stroke: 'blue',
strokeWidth: 3
},
label: {
fill: 'black'
},
'element-halo': {
display: 'initial'
}
}
},
ports: {
attributes: {
in: {
attrs: {
'.port-body': {
stroke: 'blue'
}
}
},
out: {
attrs: {
'.port-body': {
stroke: 'blue'
}
}
}
}
},
link: {
attributes: {
attrs: {
line: {
strokeDasharray: 0,
stroke: 'blue'
}
}
}
}
},
'@invalid': {
attributes: {
attrs: {
body: {
stroke: 'red',
strokeWidth: 2
},
label: {
fill: 'red'
}
}
},
ports: {
attributes: {
in: {
attrs: {
'.port-body': {
stroke: 'red'
}
}
},
out: {
attrs: {
'.port-body': {
stroke: 'red'
}
}
}
}
}
}
}
};
var customSelectionShape = {
attributes: {
markup: [
{
tagName: 'rect',
selector: 'body'
},
{
tagName: 'g',
selector: 'halo',
children: [
{
tagName: 'image',
selector: 'halo-copy'
},
{
tagName: 'image',
selector: 'halo-cut'
},
{
tagName: 'image',
selector: 'halo-remove'
}
]
}
],
attrs: {
body: {
pointerEvents: 'none',
fill: 'rgba(0, 0, 0, 0.1)',
stroke: 'black',
strokeWidth: 1
},
halo: {
ref: 'body',
width: 68,
height: 44,
refX: 0.5,
refY: 0,
refY2: -24,
xAlignment: 'middle'
},
'halo-copy': {
event: 'element-copy',
x: 0,
y: 0,
width: 20,
height: 20,
xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTUgMnYyaDRWMkg1em02IDBoMWEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJIMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmgxYTIgMiAwIDAgMSAyLTJoNGEyIDIgMCAwIDEgMiAyem0wIDJhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJIMnYxNGgxMFY0aC0xek00IDhoNmExIDEgMCAwIDEgMCAySDRhMSAxIDAgMSAxIDAtMnptMCA1aDZhMSAxIDAgMCAxIDAgMkg0YTEgMSAwIDAgMSAwLTJ6Ij48L3BhdGg+PC9zdmc+',
cursor: 'pointer'
},
'halo-cut': {
event: 'element-cut',
x: 24,
y: 0,
width: 20,
height: 20,
cursor: 'pointer',
xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yLjUgLTIuNSAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiIgY2xhc3M9Imljb25fX2ljb24iPjxwYXRoIGQ9Ik05LjEwNyAxMC41NTRMNy4zNjMgMTIuNjNhMy45OTMgMy45OTMgMCAwIDEtLjE5MiA0Ljg5IDQuMDA0IDQuMDA0IDAgMCAxLTUuNjM1LjQ5MiAzLjk5MSAzLjk5MSAwIDAgMS0uNDkzLTUuNjI4IDQuMDA1IDQuMDA1IDAgMCAxIDQuNzg4LTEuMDM4TDcuODAxIDkgMi40MTMgMi41ODdhLjk5OC45OTggMCAwIDEgLjEyMy0xLjQwNyAxIDEgMCAwIDEgMS40MDkuMTIzbDUuMTYyIDYuMTQ0IDUuMTYxLTYuMTQ0YTEgMSAwIDAgMSAxLjQxLS4xMjMuOTk4Ljk5OCAwIDAgMSAuMTIzIDEuNDA3TDEwLjQxMiA5bDEuOTcgMi4zNDVhNC4wMDUgNC4wMDUgMCAwIDEgNC43ODggMS4wMzggMy45OTEgMy45OTEgMCAwIDEtLjQ5MyA1LjYyOCA0LjAwNCA0LjAwNCAwIDAgMS01LjYzNS0uNDkyIDMuOTkzIDMuOTkzIDAgMCAxLS4xOTItNC44OWwtMS43NDMtMi4wNzV6bS02LjI4NSA1LjkyN2EyIDIgMCAwIDAgMi41NzEtMy4wNiAyLjAwMiAyLjAwMiAwIDAgMC0yLjgxOC4yNDZjLS43MS44NDUtLjYgMi4xMDUuMjQ3IDIuODE0em0xMi41NyAwYTEuOTk2IDEuOTk2IDAgMCAwIC4yNDYtMi44MTQgMi4wMDIgMi4wMDIgMCAwIDAtMi44MTctLjI0NiAxLjk5NiAxLjk5NiAwIDAgMC0uMjQ3IDIuODE0Yy43MS44NDUgMS45NzIuOTU1IDIuODE4LjI0NnoiPjwvcGF0aD48L3N2Zz4'
},
'halo-remove': {
event: 'element-remove',
x: 48,
y: 0,
width: 20,
height: 20,
cursor: 'pointer',
xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zIC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTYgMlYxYTEgMSAwIDAgMSAxLTFoNGExIDEgMCAwIDEgMSAxdjFoNGEyIDIgMCAwIDEgMiAydjFhMiAyIDAgMCAxLTIgMmgtLjEzM2wtLjY4IDEwLjJhMyAzIDAgMCAxLTIuOTkzIDIuOEg1LjgyNmEzIDMgMCAwIDEtMi45OTMtMi43OTZMMi4xMzcgN0gyYTIgMiAwIDAgMS0yLTJWNGEyIDIgMCAwIDEgMi0yaDR6bTEwIDJIMnYxaDE0VjR6TTQuMTQxIDdsLjY4NyAxMC4wNjhhMSAxIDAgMCAwIC45OTguOTMyaDYuMzY4YTEgMSAwIDAgMCAuOTk4LS45MzRMMTMuODYyIDdoLTkuNzJ6TTcgOGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6bTQgMGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6Ij48L3BhdGg+PC9zdmc+'
}
}
}
};
new Appmixer({
componentShapes: {
action: customComponentShape,
trigger: customComponentShape,
selection: customSelectionShape
}
});
// or any library you want to perform API requests
const request = require('request-promise');
module.exports = {
// there will be some other properties based on the authentication 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' (https://www.npmjs.com/package/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}}'
// if you define 'requestAccessToken' property in an Oauth2 authentication
// module with a string that contains a URL, then a POST request will be \
// sent to that URL. In other words, what will happen, when a property
// is defined as a string, depends on the context (Oauth vs 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 context.httpRequest({
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 context.httpRequest({
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}}'
}
}
}
};
validate: {
'method': 'GET',
'uri': 'https://api.apify.com/v2/users/{{apiUserId}}?token={{apiToken}}'
},
validateErrCallback: err => {
if (err?.someNestedObject?.message) {
throw new Error(err.someNestedObject.message); // message for the UI
}
throw err;
}'use strict';
module.exports = {
type: 'pwd',
definition: {
// As opposed to the 'apiKey' module, the 'auth' property inside the
// definition object is optional for the 'pwd' type. If omitted, it will
// have the following structure. If you want to change name of the field,
// change the content of the tooltip or add another field, you can do
// that here.
auth: {
username: {
type: 'text',
name: 'Username',
tooltip: 'Username'
},
password: {
type: 'password',
name: 'Password',
tooltip: 'Password'
}
},
// the only mandatory property for the 'pwd' authentication type
validate: async context => {
const { username, password } = context
const { data } = await context.httpRequest.post(
// the URL for the username/password authentication
'https://service-server-api-url/auth',
{ username, password }
);
// verify authentication works
if (!data.isValid) {
throw new Error("Invalid username/password combination.");
}
// if the API does not return a token for exchange, then return here.
// in such case, the components using this authentication module
// will have to use the username/password to perform each request.
// There will be properties context.auth.username and
// context.auth.password available in the context object in a component.
// if the API returns a token and expiration then return them using
// the following way - object with 'token' and 'expires' properties.
const { token, expiresIn } = data;
// the token and expiration (if provided) will be available in a
// component's context object under context.auth.token and
// context.auth.expires
return {
token,
// expires can be a Date - token expiration date (timestamp),
// or number/string with seconds representing a token lifetime.
// Appmixer will use this to request a new token (using stored
// username and password) when the current one is about to expire
expires: expiresIn
};
// if the API returns a token that does not expire, just return
// that token
// return { token };
}
}
};
'use strict';
module.exports = {
type: 'pwd',
definition: {
validate: async context => {
await context.httpRequest.get('https://postman-echo.com/basic-auth', {
auth: {
username: context.username,
password: context.password
}
});
}
}
}'use strict';
module.exports = {
async start(context) {
await context.httpRequest.get('https://postman-echo.com/basic-auth', {
auth: {
username: context.auth.username,
password: context.auth.password
}
});
}
}'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}}'
}
}
}
};
module.exports = {
type: 'oauth2',
definition: () => {
// telling Appmixer it cannot refresh it sooner than 30 seconds
// before access token expiration
refreshBeforeExp: 30,
refreshBeforeExpUnits: 'seconds', // 'minutes' by default
// the rest is business as usual
authUrl: 'https://www.acme.com/oaut2/authorize'
...
}
}module.exports = {
type: 'pwd',
definition: {
// the only mandatory property for the 'pwd' authentication type
validate: async context => {
const { username, password } = context;
const { data } = await context.httpRequest.post(
// the URL for the username/password authentication
'https://service-server-api-url/auth',
{ username, password }
);
// verify authentication works
if (!data.isValid) {
throw new Error("Invalid username/password combination.");
}
return true;
}
}
};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 Promise = require('bluebird');
module.exports = {
async receive(context) {
// this is the old way, without context.sendArray function
const arrayOfObjects = [
{ name: 'John', surname: 'Doe' },
{ name: 'Martin', surname: 'Tester' }
];
await Promise.map(arrayOfObjects, item => {
return context.sendJson(item, 'out');
});
// and the new way
await context.sendArray(arrayOfObjects, 'out');
// by default it will be sending 5 items in parallel to the
// output port. That default amount depends on Appmixer
// configuration
// the concurrency can be changed
await context.sendArray(arrayOfObjects, 'out', {
// sending one by one
concurrency: 1
});
}
}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 = {
async receive(context) {
const { data } = await context.httpRequest({
url: "https://some-url.com/api",
method: "POST",
data: {
username: "someuser",
password: "somepass",
},
headers: { Authentication: "some"}
});
// it is also possible to execute dedicated http methods
// see here https://axios-http.com/docs/api_intro
// for example
// context.httpRequest.get(url[, config])
const { data } = await context.httpRequest.get("https://url", { params: { name: "some" }, headers: {}});
// context.httpRequest.post(url[, data[, config]])
const { data } = await context.httpRequest.post("https://url", { username: "some" }, headers: {});
return context.response(data);
}
}await context.store.find(storeId, { query: { key: { $nin: rowIds } } })module.exports = {
async receive(context) {
// whenever there is an item added/updated/removed from the data
// store, this component will get triggered with data on the
// context.messages.webhook.content
const data = context.messages.webhook.content.data.currentValue;
if (context.messages.webhook.content.data.type === 'insert') {
await context.sendJson({
key: data.key,
storeId: data.storeId,
value: data.value,
updatedAt: data.updatedAt,
createdAt: data.createdAt
}, 'item');
}
return context.response('ok');
},
async start(context) {
// register without specifying 'events'.
await context.store.registerWebhook(context.properties.storeId);
},
async stop(context) {
await context.store.unregisterWebhook(context.properties.storeId);
}
};module.exports = {
async receive(context) {
const data = context.messages.webhook.content.data.currentValue;
await context.sendJson({
key: data.key,
storeId: data.storeId,
value: data.value,
updatedAt: data.updatedAt,
createdAt: data.createdAt
}, 'item');
return context.response('ok');
},
async start(context) {
// register only the 'insert' events on the data store.
await context.store.registerWebhook(context.properties.storeId, ['insert']);
},
async stop(context) {
await context.store.unregisterWebhook(context.properties.storeId);
}
};
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;
}
}
};





