diff --git a/custom_connectors/oauth2/drift_connector.rb b/custom_connectors/oauth2/drift_connector.rb
new file mode 100644
index 00000000..d6dba3e5
--- /dev/null
+++ b/custom_connectors/oauth2/drift_connector.rb
@@ -0,0 +1,2322 @@
+{
+ title: 'Drift',
+
+ connection: {
+ fields: [
+ {
+ name: 'client_id',
+ type: 'string',
+ hint: 'Create a new app to generate client id and secret. ' \
+ "click here to create app.",
+ optional: false
+ },
+ {
+ name: 'client_secret',
+ control_type: 'password',
+ type: 'string',
+ hint: 'Create a new app to generate client id and secret. ' \
+ "click here to create app.",
+ optional: false
+ },
+ {
+ name: 'verification_token',
+ control_type: 'password',
+ type: 'string',
+ hint: 'Use verification token to receive interactive messages, click' \
+ "here" \
+ ' to find verification token.',
+ optional: true
+ }
+ ],
+ authorization: {
+ type: 'oauth2',
+ authorization_url: lambda do |connection|
+ 'https://dev.drift.com/authorize?response_type=code&' \
+ "client_id=#{connection['client_id']}" \
+ end,
+ acquire: lambda do |connection, auth_code, redirect_uri|
+ post('https://driftapi.com/oauth2/token').
+ payload(client_id: connection['client_id'],
+ client_secret: connection['client_secret'],
+ grant_type: 'authorization_code',
+ code: auth_code,
+ redirect_uri: redirect_uri).
+ request_format_www_form_urlencoded
+ end,
+ refresh: lambda do |connection, refresh_token|
+ post('https://driftapi.com/oauth2/token').
+ payload(client_id: connection['client_id'],
+ client_secret: connection['client_secret'],
+ grant_type: 'refresh_token',
+ refresh_token: refresh_token).
+ request_format_www_form_urlencoded
+ end,
+
+ refresh_on: [401, 403],
+ apply: lambda do |_connection, access_token|
+ headers(Authorization: "Bearer #{access_token}")
+ end
+ },
+ base_uri: lambda {
+ 'https://driftapi.com'
+ }
+ },
+
+ test: lambda { |_connection|
+ get('/users/list')
+ },
+
+ methods: {
+ contact_attributes_schema: lambda do |_input|
+ attributes =
+ get('/contacts/attributes')&.dig('data', 'properties')&.map do |attribute|
+ field = {
+ name: attribute['name'],
+ label: attribute['name'].labelize,
+ sticky: true
+ }
+ case attribute['type']
+ when 'BOOLEAN'
+ { type: 'boolean', control_type: 'checkbox',
+ toggle_hint: 'Select from list',
+ toggle_field: {
+ name: attribute['name'],
+ label: attribute['name'].labelize,
+ type: 'string',
+ optional: true,
+ control_type: 'boolean',
+ render_input: 'boolean_conversion',
+ parse_output: 'boolean_conversion',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false.'
+ } }
+ when 'DATE'
+ { type: 'date' }
+ when 'DATETIME'
+ { type: 'date_time' }
+ when 'NUMERIC'
+ { type: 'number' }
+ else
+ {}
+ end&.merge(field)
+ end
+ attributes.concat(
+ [
+ { name: '_end_user_version', label: 'End user version', type: 'integer' },
+ { name: '_calculated_version', label: 'Calculated version', type: 'integer' },
+ { name: 'original_conversation_started_page_title' },
+ { name: 'externalId' },
+ { name: 'tags', type: 'array', of: 'object',
+ properties: [
+ { name: 'name',
+ hint: 'The display name of the Tag. This is effectively a key for the Tag.' },
+ { name: 'color', hint: 'Hexadecimal color for the Tag.' }
+ ] },
+ { name: 'events', type: 'object' },
+ {
+ name: 'socialProfiles', type: 'object',
+ properties: [
+ {
+ name: 'indexedAt',
+ type: 'date_time'
+ },
+ {
+ name: 'github',
+ type: 'object',
+ properties: [
+ { name: 'handle' },
+ { name: 'id' },
+ { name: 'avatar' },
+ { name: 'company' },
+ { name: 'blog' },
+ { name: 'followers' },
+ { name: 'following' }
+ ]
+ },
+ {
+ name: 'facebook',
+ type: 'object',
+ properties: [
+ { name: 'handle' }
+ ]
+
+ },
+ {
+ name: 'employment',
+ type: 'object',
+ properties: [
+ { name: 'domain' },
+ { name: 'name' },
+ { name: 'title' },
+ { name: 'role' },
+ { name: 'seniority' }
+ ]
+ },
+ {
+ name: 'linkedin',
+ type: 'object',
+ properties: [
+ { name: 'handle' }
+ ]
+ },
+ {
+ name: 'aboutme',
+ type: 'object',
+ properties: [
+ { name: 'handle' },
+ { name: 'bio' },
+ { name: 'avatar' }
+ ]
+ },
+ {
+ name: 'geo',
+ label: 'GEO location',
+ type: 'object',
+ properties: [
+ { name: 'city' },
+ { name: 'state' },
+ { name: 'stateCode' },
+ { name: 'country' },
+ { name: 'countryCode' },
+ { name: 'lat',
+ label: 'Latitude',
+ type: 'integer' },
+ { name: 'lng',
+ label: 'Longitude',
+ type: 'integer' }
+ ]
+ },
+ {
+ name: 'twitter',
+ type: 'object',
+ properties: [
+ { name: 'handle' },
+ { name: 'id' },
+ { name: 'bio',
+ label: 'Biography' },
+ { name: 'followers' },
+ { name: 'following' },
+ { name: 'statuses' },
+ { name: 'favorites' },
+ { name: 'location' },
+ { name: 'site' },
+ { name: 'avatar' }
+ ]
+ },
+ { name: 'activeAt',
+ type: 'date_time' },
+ {
+ name: 'emailProvider',
+ type: 'boolean',
+ control_type: 'checkbox',
+ label: 'Email provider',
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'emailProvider',
+ label: 'Email provider',
+ type: 'boolean',
+ control_type: 'text',
+ hint: 'Allowed values are: true, false',
+ toggle_hint: 'Use custom value'
+ }
+ },
+ {
+ name: 'gravatar',
+ label: 'Gravatar',
+ type: 'object',
+ properties: [
+ { name: 'handle' },
+ { name: 'urls' },
+ { name: 'avatar' }
+ ]
+ },
+ {
+ name: 'name',
+ type: 'object',
+ properties: [
+ { name: 'fullName' },
+ { name: 'givenName' },
+ { name: 'familyName' }
+ ]
+ },
+ { name: 'id' },
+ {
+ control_type: 'email',
+ name: 'email'
+ },
+ {
+ name: 'fuzzy',
+ label: 'Fuzzy',
+ type: 'boolean',
+ control_type: 'checkbox',
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'fuzzy',
+ type: 'boolean',
+ optional: true,
+ control_type: 'text',
+ label: 'Fuzzy',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ }
+ },
+ {
+ name: 'googleplus',
+ type: 'object',
+ properties: [
+ { name: 'handle' }
+ ]
+ }
+ ]
+
+ }
+ ]
+ )
+ end,
+
+ get_contact_schema: lambda do |_input|
+ [
+ {
+ name: 'id',
+ control_type: 'integer',
+ type: 'integer',
+ label: 'Contact ID',
+ optional: false
+ },
+ {
+ name: 'createdAt',
+ control_type: 'date_time',
+ type: 'date_time'
+ },
+ {
+ name: 'attributes',
+ type: 'object',
+ properties: call('contact_attributes_schema', '').
+ ignored('_end_user_version', '_calculated_version', 'original_conversation_started_page_title')
+ }
+ ]
+ end,
+
+ get_contact_tag_schema: lambda do |_input|
+ [
+ { name: 'name' },
+ { name: 'color' }
+ ]
+ end,
+
+ get_message_schema: lambda do |_input|
+ [
+ {
+ name: 'id',
+ label: 'Message ID',
+ type: 'integer',
+ control_type: 'number',
+ render_input: 'integer_conversion',
+ parse_output: 'integer_conversion',
+ hint: 'A unique identifier for the message.'
+ },
+ {
+ name: 'orgId',
+ type: 'integer',
+ control_type: 'number',
+ label: 'Organization ID',
+ render_input: 'integer_conversion',
+ parse_output: 'integer_conversion'
+ },
+ {
+ name: 'author',
+ label: 'Author',
+ type: 'object',
+ hint: 'An object describing who authored this Message.',
+ properties: [
+ {
+ control_type: 'number',
+ parse_output: 'float_conversion',
+ type: 'integer',
+ name: 'id',
+ optional: false
+ },
+ {
+ control_type: 'select',
+ name: 'type',
+ pick_list: [
+ %w[Contact contact],
+ %w[User user]
+ ],
+ toggle_hint: 'Select from list',
+ toggle_field: {
+ name: 'type',
+ label: 'Type',
+ optional: true,
+ type: 'string',
+ control_type: 'text',
+ hint: 'Allowed values: user, contact',
+ toggle_hint: 'Use custom value'
+ }
+ },
+ { name: 'bot', type: 'boolean' }
+ ]
+ },
+ { name: 'body',
+ sticky: true,
+ hint: 'The text contents of this message.' },
+ {
+ control_type: 'select',
+ label: 'Type',
+ name: 'type',
+ hint: 'Specifies the type of this message.',
+ pick_list: [
+ %w[Chat chat],
+ ['Private note', 'private_note'],
+ ['Private prompt', 'private_prompt'],
+ %w[Edit edit]
+ ],
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'type',
+ type: 'string',
+ label: 'Type',
+ control_type: 'text',
+ hint: 'Allowed values: private_note, private_prompt,' \
+ ' chat and edit.',
+ toggle_hint: 'Use custom value'
+ }
+ },
+ {
+ control_type: 'number',
+ label: 'Conversation ID',
+ hint: 'The ID field of the conversation this message is a part of.',
+ render_input: 'integer_conversion',
+ parse_output: 'integer_conversion',
+ type: 'integer',
+ name: 'conversationId'
+ },
+ {
+ control_type: 'date_time',
+ type: 'date_time',
+ name: 'createdAt'
+ },
+ {
+ name: 'attachment',
+ type: 'array',
+ of: 'object',
+ label: 'Attachment',
+ properties: [
+ { name: 'fileName' },
+ {
+ label: 'MIME type',
+ name: 'mimeType'
+ },
+ {
+ control_type: 'url',
+ label: 'URL',
+ name: 'url'
+ }
+ ]
+ },
+ {
+ name: 'buttons',
+ type: 'array',
+ sticky: true,
+ of: 'object',
+ properties: [
+ { name: 'value',
+ sticky: true,
+ hint: 'For Reply type buttons, value must equal Label.
In Compose types, ' \
+ "value is what is entered into the User's composer.
In Action types, value should be a " \
+ 'slug that identifies the sort of action intended by the button click; this is interpreted ' \
+ 'by an App to properly react to a button click.' },
+ { name: 'label',
+ sticky: true,
+ hint: 'For all button types, the Label field is what is actually displayed as a ' \
+ 'label on the button to the User or Contact.' },
+ { control_type: 'select',
+ label: 'Type',
+ name: 'type',
+ sticky: true,
+ hint: 'Reply type buttons cause the text in the button to be immediately used as a new chat ' \
+ 'message by the contact when pressed. Reply type buttons are only ever rendered to contacts' \
+ '(Users will only see the body of the message.) and are the only type allowed in chat messages.
' \
+ "
Compose type buttons cause text be added to a user's conversation composer. A user can " \
+ 'then edit the message before they get sent. They are only allowed in private_prompt Messages.
' \
+ '
Action type buttons signal a button click to Apps via their webhook. ' \
+ 'They are only allowed in private prompt messages.',
+ pick_list: [
+ %w[Reply reply],
+ %w[Compose compose],
+ %w[Action action]
+ ],
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'type',
+ type: 'string',
+ optional: true,
+ label: 'Type',
+ control_type: 'text',
+ hint: 'Allowed values: reply, compose, and action.',
+ toggle_hint: 'Use custom value'
+ } },
+ { control_type: 'select',
+ label: 'Style',
+ name: 'style',
+ sticky: true,
+ hint: 'This is only applicable to buttons within private prompt messages.
When the value is' \
+ ' Primary, the button is rendered as green with white text.
When the value is ' \
+ "Danger, the button is rendered as red with white text.
If style isn't specified, " \
+ 'a style fitting with the overall design of the Drift UI is used.',
+ pick_list: [
+ %w[Primary primary],
+ %w[Danger danger]
+ ],
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'style',
+ type: 'string',
+ optional: true,
+ label: 'Style',
+ control_type: 'text',
+ hint: 'Allowed values: primary and danger.',
+ toggle_hint: 'Use custom value'
+ } },
+ { name: 'reaction',
+ type: 'object',
+ sticky: true,
+ hint: 'A Reaction can be specified to automatically cause a visual change when a button ' \
+ 'is pressed.
If Reaction type is Delete, the message containing the button ' \
+ 'is (visually) deleted when the button is hit.
If Reaction type is Replace, ' \
+ 'the message is replaced with one containing only a message body set to the value of ' \
+ 'Reaction message. If the type is replace, Reaction message must be specified.',
+ properties: [
+ { control_type: 'select',
+ label: 'Type',
+ name: 'type',
+ sticky: true,
+ pick_list: [
+ %w[Replace replace],
+ %w[Delete delete]
+ ],
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'type',
+ type: 'string',
+ optional: true,
+ label: 'Type',
+ control_type: 'text',
+ hint: 'Allowed values: replace and delete.',
+ toggle_hint: 'Use custom value'
+ } },
+ { name: 'message',
+ sticky: true }
+ ] }
+ ]
+ },
+ {
+ properties: [
+ { label: 'IP adress', name: 'ip' },
+ { name: 'userAgent' },
+ { name: 'title' },
+ { name: 'referrer' },
+ { name: 'timezone' },
+ {
+ properties: [
+ { name: 'city' },
+ { name: 'region' },
+ { name: 'country' },
+ { name: 'countryName' },
+ { name: 'postalCode' },
+ {
+ control_type: 'number',
+ parse_output: 'float_conversion',
+ type: 'number',
+ name: 'latitude'
+ },
+ {
+ control_type: 'number',
+ parse_output: 'float_conversion',
+ type: 'number',
+ name: 'longitude'
+ }
+ ],
+ type: 'object',
+ name: 'location'
+ },
+ { name: 'locale' }
+ ],
+ type: 'object',
+ name: 'context'
+ },
+ {
+ control_type: 'integer',
+ label: 'Edited message ID',
+ hint: 'Specifies the ID of the message being edited.',
+ sticky: true,
+ render_input: 'integer_conversion',
+ parse_output: 'integer_conversion',
+ type: 'integer',
+ name: 'editedMessageId'
+ },
+ {
+ control_type: 'select',
+ label: 'Edit type',
+ hint: 'Specifies the type of edit to be performed. ' \
+ "Click here to learn more.",
+ sticky: true,
+ name: 'editType',
+ pick_list: [
+ %w[Delete delete],
+ %w[Replace replace],
+ ['Replace body', 'replace_body'],
+ ['Replace buttons', 'replace_buttons']
+ ],
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'type',
+ type: 'string',
+ label: 'Edit type',
+ optional: true,
+ control_type: 'string',
+ hint: 'Allowed values: delete, replace, replace_body, ' \
+ 'and replace_buttons.',
+ toggle_hint: 'Use custom value'
+ }
+ },
+ {
+ name: 'attributes',
+ type: 'object',
+ properties: [
+ { name: 'preMessages', type: 'object', properties: [] },
+ { name: 'developer_app_id' }
+ ]
+ },
+ { name: 'userId',
+ sticky: true,
+ hint: 'If userId not specified, the bot user for the account ' \
+ 'will be used as the message author.' }
+ ]
+ end,
+
+ get_conversation_schema: lambda do |_input|
+ [
+ {
+ name: 'id',
+ control_type: 'number',
+ type: 'integer',
+ hint: 'Conversations are uniquely identified by an id, which can be' \
+ ' used to check its current state and its messages.'
+ },
+ {
+ name: 'orgId',
+ type: 'integer',
+ control_type: 'number',
+ label: 'Organization ID'
+ },
+ {
+ name: 'participants',
+ type: 'array',
+ of: 'integer'
+ },
+ {
+ name: 'status',
+ control_type: 'select',
+ toggle_hint: 'Select from list',
+ pick_list: 'status_list',
+ toggle_field: {
+ name: 'status',
+ label: 'Status',
+ optional: true,
+ control_type: 'string',
+ type: 'string',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values: open, closed, pending'
+ }
+ },
+ {
+ name: 'contactId',
+ type: 'integer',
+ control_type: 'number',
+ parse_output: 'integer_conversion'
+ },
+ {
+ name: 'createdAt',
+ type: 'date_time',
+ control_type: 'date_time',
+ label: 'Created at',
+ render_input: 'integer_conversion',
+ parse_output: 'date_time_conversion'
+ },
+ {
+ name: 'updatedAt',
+ type: 'date_time',
+ control_type: 'date_time',
+ render_input: 'integer_conversion',
+ parse_output: 'date_time_conversion'
+ },
+ {
+ name: 'inboxId',
+ type: 'integer',
+ control_type: 'number'
+ },
+ { name: 'conversationTags', type: 'array', of: 'object',
+ properties: [
+ { name: 'color' },
+ { name: 'name' }
+ ] },
+ { name: 'relatedPlaybookId',
+ label: 'Related playbook ID',
+ hint: 'This is the ID of the playbook that initiated the ' \
+ 'conversation.' }
+ ]
+ end,
+
+ get_user_schema: lambda do |_input|
+ [
+ {
+ label: 'User ID',
+ type: 'integer',
+ name: 'id',
+ hint: 'The Drift identifier for the user. This is will always be numeric.'
+ },
+ {
+ label: 'Organization ID',
+ type: 'integer',
+ name: 'orgId',
+ optional: false
+ },
+ { name: 'name', hint: 'The name of the user.', sticky: true },
+ { name: 'alias', hint: 'The alias of the user.', sticky: true },
+ { name: 'email', control_type: 'email', hint: 'The email address of the user.', sticky: true },
+ {
+ name: 'availability',
+ sticky: true,
+ control_type: 'select',
+ pick_list: [
+ %w[Available AVAILABLE],
+ %w[Offline OFFLINE]
+ ],
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ type: 'string',
+ name: 'availability',
+ label: 'Availability',
+ optional: true,
+ control_type: 'string',
+ hint: 'Allowed values: AVAILABLE, OFFLINE',
+ toggle_hint: 'Use custom value'
+ }
+ },
+ { name: 'role', control_type: 'select',
+ pick_list: 'roles_list',
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'role',
+ type: 'string',
+ optional: true,
+ control_type: 'text',
+ label: 'Role',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values: member, admin, and agent. '
+ } },
+ { name: 'timeZone' },
+ { name: 'avatarUrl', sticky: true, hint: 'The URL pointing to the avatar image of the user.' },
+ { name: 'phone', control_type: 'phone', label: 'Phone number', sticky: true,
+ hint: 'Enter phone number in the form of XXX-XXX-XXXX.' },
+ {
+ name: 'verified',
+ type: 'boolean',
+ control_type: 'checkbox',
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'verified',
+ type: 'boolean',
+ optional: true,
+ control_type: 'text',
+ label: 'Verified',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values: true, false'
+ }
+ },
+ {
+ name: 'bot',
+ type: 'boolean',
+ control_type: 'checkbox',
+ toggle_hint: 'Select from option list',
+ toggle_field: {
+ name: 'bot',
+ type: 'boolean',
+ optional: true,
+ label: 'Bot',
+ control_type: 'boolean',
+ hint: 'Allowed values: true, false',
+ toggle_hint: 'Use custom value'
+ }
+ },
+ {
+ name: 'createdAt',
+ control_type: 'date_time',
+ label: 'Created time',
+ type: 'date_time'
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated time',
+ type: 'date_time',
+ control_type: 'date_time'
+ }
+ ]
+ end,
+
+ get_account_schema: lambda do |_input|
+ [
+ { name: 'ownerId',
+ sticky: true,
+ hint: 'The ID of the owner in Drift (should be a known user ID).' },
+ { name: 'name', label: 'Company name', sticky: true },
+ { name: 'domain', sticky: true, label: 'Domain name' },
+ { name: 'accountId', sticky: true },
+ { name: 'deleted', type: 'boolean', control_type: 'checkbox',
+ toggle_hint: 'Select from list',
+ toggle_field: {
+ name: 'deleted',
+ type: 'string',
+ optional: true,
+ label: 'Deleted',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values: true, false'
+ } },
+ { name: 'createDateTime', type: 'date_time' },
+ { name: 'updateDateTime', type: 'integer' },
+ { name: 'targeted', type: 'boolean', control_type: 'checkbox',
+ hint: 'Is the account currently targeted.',
+ sticky: true,
+ toggle_hint: 'Select from list',
+ toggle_field: {
+ name: 'targeted',
+ type: 'string',
+ optional: true,
+ label: 'Targeted',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values: true, false'
+ } },
+ { name: 'customProperties', type: 'array', of: 'object', sticky: true,
+ properties: [
+ { name: 'label', sticky: true,
+ hint: 'The readable name of the property.' },
+ { name: 'name', sticky: true,
+ hint: 'The internal name of the property.' },
+ { name: 'value', sticky: true,
+ hint: 'The value of the property.' },
+ { name: 'type', sticky: true,
+ control_type: 'select',
+ pick_list: 'data_types',
+ toggle_hint: 'Select data type',
+ toggle_field: {
+ name: 'type',
+ type: 'Type',
+ optional: true,
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values: ' \
+ 'STRING, EMAIL, NUMBER, TEAMMEMBER, ENUM, DATE, DATETIME, ' \
+ 'LATLON, LAT, LON, PHONE, URL and ENUMARRAY.'
+ },
+ hint: 'Data type of the property.' }
+ ] }
+ ]
+ end,
+
+ get_playbook_schema: lambda do |_input|
+ [
+ { name: 'id', type: 'integer', label: 'Playbook ID' },
+ { name: 'name' },
+ { name: 'orgId', type: 'integer', label: 'Organization ID' },
+ { name: 'meta', type: 'object' },
+ { name: 'createdAt', type: 'date_time' },
+ { name: 'updatedAt', type: 'date_time' },
+ { name: 'createdAuthorId', type: 'integer',
+ hint: 'ID of the user that created the playbook' },
+ { name: 'updatedAuthorId', type: 'integer',
+ hint: 'ID of the user that last updated the playbook' },
+ { name: 'interactionId', type: 'integer' },
+ { name: 'reportType' },
+ { name: 'goals', type: 'array', of: 'object', properties: [
+ { name: 'id', label: 'Goal ID' },
+ { name: 'message' }
+ ] }
+ ]
+ end,
+
+ get_meeting_schema: lambda do |_input|
+ [
+ { name: 'agentId', type: 'integer' },
+ { name: 'orgId', type: 'integer', label: 'Organization identifier' },
+ { name: 'status', hint: 'e.g. "ACTIVE", state of the meeting' },
+ { name: 'meetingSource',
+ hint: 'e.g. "EMAIL_DROP", source of where the meeting was booked' },
+ { name: 'schedulerId', type: 'integer' },
+ { name: 'eventId' },
+ { name: 'slug' },
+ { name: 'slotStart', type: 'date_time',
+ label: 'Slot start date' },
+ { name: 'slotEnd', type: 'date_time',
+ label: 'Slot end date' },
+ { name: 'updatedAt', type: 'date_time' },
+ { name: 'scheduledAt', type: 'date_time' },
+ { name: 'meetingType',
+ hint: 'e.g. "New Meeting", stating the type of the meeting.' },
+ { name: 'conversationId', type: 'integer' },
+ { name: 'endUserTimeZone' },
+ { name: 'meetingNotes' },
+ { name: 'bookedBy', hint: 'Drift identifier for the user if present.' },
+ { name: 'eventType' }
+ ]
+ end,
+
+ get_command_message_schema: lambda do |_input|
+ [
+ { name: 'orgId', type: 'integer', label: 'Organization ID' },
+ { name: 'type' },
+ { name: 'Message', type: 'object', properties: call('get_message_schema') }
+ ]
+ end,
+
+ get_timeline_schema: lambda do |_input|
+ [
+ { name: 'contactId', type: 'integer', sticky: true,
+ hint: 'Either Contact ID or External ID must be present.' },
+ { name: 'createdAt', type: 'date_time', sticky: true,
+ hint: 'CreatedAt time can be excluded from the request (will default to the time of send).' },
+ { name: 'externalId', type: 'integer', sticky: true,
+ hint: 'Either Contact ID or External ID must be present.' },
+ { name: 'event', sticky: true,
+ hint: 'Example: "New External Event from <\your app\>"' },
+ { name: 'attributes', type: 'array', of: 'object', properties: [
+ { name: 'key' },
+ { name: 'value' }
+ ] }
+ ]
+ end,
+
+ convert_input_to_date: lambda do |input|
+ date_fields = %w[start_date last_active last_contacted scheduledAt slotStart
+ slotEnd updatedAt indexedAt createdAt createDateTime updateDateTime]
+ if input.is_a?(Array)
+ input.map do |array_value|
+ call('convert_input_to_date', array_value)
+ end
+ elsif input.is_a?(Hash)
+ input.map do |key, value|
+ value = call('convert_input_to_date', value)
+ if date_fields.include?(key)
+ { key => (value.to_i / 1000).to_i.to_time.utc }
+ else
+ { key => value }
+ end
+ end.inject(:merge)
+ else
+ input
+ end
+ end,
+
+ format_unix_to_utc_time: lambda do |input|
+ date_fields = %w[start_date last_active last_contacted scheduledAt slotStart timestamp
+ slotEnd updatedAt indexedAt createdAt createDateTime updateDateTime]
+ if input.is_a?(Array)
+ input.map do |array_value|
+ call('format_unix_to_utc_time', array_value)
+ end
+ elsif input.is_a?(Hash)
+ input.map do |key, value|
+ value = call('format_unix_to_utc_time', value)
+ if date_fields.include?(key)
+ { key => (value.to_i / 1000).to_i.to_time.utc }
+ else
+ { key => value }
+ end
+ end.inject(:merge)
+ else
+ input
+ end
+ end,
+
+ format_date_time_to_unix: lambda do |input|
+ date_fields = %w[start_date last_active last_contacted scheduledAt slotStart slotEnd
+ updatedAt indexedAt createdAt createDateTime updateDateTime]
+ if input.is_a?(Array)
+ input.map do |array_value|
+ call('format_date_time_to_unix', array_value)
+ end
+ elsif input.is_a?(Hash)
+ input.map do |key, value|
+ value = call('format_date_time_to_unix', value)
+ if date_fields.include?(key)
+ { key => value.to_i * 1000 }
+ else
+ { key => value }
+ end
+ end.inject(:merge)
+ else
+ input
+ end
+ end,
+
+ search_contacts_schema: lambda do |_input|
+ [
+ { name: 'email', control_type: 'email', sticky: true },
+ { name: 'externalID', sticky: true },
+ { name: 'limit', type: 'integer', label: 'Page size', sticky: true,
+ hint: 'Sets a maximum number of contacts returned.' }
+ ]
+ end,
+
+ search_conversations_schema: lambda do |_input|
+ [
+ {
+ name: 'statusId',
+ type: 'string',
+ control_type: 'multiselect',
+ pick_list_params: {},
+ delimiter: ',',
+ label: 'Status',
+ sticky: true,
+ toggle_hint: 'Select from list',
+ pick_list: 'status_id_list',
+ toggle_field: {
+ name: 'statusId',
+ label: 'Status IDs',
+ control_type: 'string',
+ type: 'string',
+ sticky: true,
+ toggle_hint: 'Use custom value',
+ hint: 'Use comma separated list of statuses. Allowed values: 1 - Open, 2 - Closed, 3 - Pending'
+ }
+ },
+ { name: 'limit', type: 'integer', sticky: true,
+ hint: 'Maximum number of conversations to retrieve (maximum=50, default=25).' },
+ { name: 'next', type: 'integer', sticky: true, label: 'Offset',
+ hint: 'Retrieves open and closed conversations (after the cursor value 50): as' \
+ ' in (statusId=1&statusId=2&next=50)' }
+ ]
+ end,
+
+ search_accounts_schema: lambda do |_input|
+ [
+ { name: 'size', type: 'integer', label: 'Limit', sticky: true,
+ hint: 'Maximum number of accounts to retrieve (maximum=100, default=10).' },
+ { name: 'index', type: 'integer', label: 'Offset', sticky: true,
+ hint: "Used as a starting index of the query of the accounts in the authenticated Drift user's account." }
+ ]
+ end,
+
+ search_meetings_schema: lambda do |_input|
+ [
+ { name: 'min_start_time', type: 'date_time', optional: false,
+ label: 'Minimum Start date' },
+ { name: 'max_start_time', type: 'date_time', optional: false,
+ label: 'Maximum Start date' }
+ ]
+ end,
+
+ create_contacts_schema: lambda do |_input|
+ call('contact_attributes_schema', '').required('email').
+ ignored('_end_user_version', '_calculated_version', 'original_conversation_started_page_title',
+ 'externalId', 'tags', 'events', 'socialProfiles')
+ end,
+
+ create_conversations_schema: lambda do |_input|
+ [
+ { name: 'email',
+ hint: 'The email address of the contact.',
+ optional: false },
+ { name: 'message', type: 'object',
+ sticky: true, optional: false,
+ properties: call('get_message_schema', '').
+ only('userId', 'editType', 'buttons', 'editedMessageId', 'type', 'body').
+ required('type').
+ concat([{ name: 'attributes',
+ type: 'object',
+ properties: [{ name: 'integrationSource', sticky: true,
+ hint: 'Example: "Message from facebook"' }] }]) }
+ ]
+ end,
+
+ create_messages_schema: lambda do |_input|
+ call('get_message_schema', '').
+ only('userId', 'editType', 'buttons', 'editedMessageId', 'conversationId', 'type', 'body').
+ required('type', 'conversationId')
+ end,
+
+ create_accounts_schema: lambda do |_input|
+ call('get_account_schema', '').
+ only('ownerId', 'name', 'domain', 'customProperties', 'targeted').
+ required('ownerId')
+ end,
+
+ create_timeline_schema: lambda do |_input|
+ call('get_timeline_schema', '').ignored('attributes')
+ end,
+
+ update_contacts_schema: lambda do |_input|
+ call('get_contact_schema', '').
+ ignored('createdAt').
+ required('id', 'attributes')
+ end,
+
+ update_users_schema: lambda do |_input|
+ call('get_user_schema', '').required('id').
+ only('id', 'name', 'alias', 'email', 'phone', 'locale', 'avatarUrl', 'availability')
+ end,
+
+ update_accounts_schema: lambda do |_input|
+ call('get_account_schema', '').
+ only('ownerId', 'name', 'domain', 'customProperties', 'targeted', 'accountId').
+ required('accountId', 'ownerId')
+ end,
+
+ make_schema_builder_fields_sticky: lambda do |input|
+ input.map do |field|
+ if field[:properties].present?
+ field[:properties] = call('make_schema_builder_fields_sticky',
+ field[:properties])
+ elsif field['properties'].present?
+ field['properties'] = call('make_schema_builder_fields_sticky',
+ field['properties'])
+ end
+ field[:sticky] = true
+ field
+ end
+ end,
+
+ validate_search_criteria: lambda do |input|
+ case input['object_name']
+ when 'contacts'
+ if input['email'].blank? && input['externalID'].blank?
+ error('Provide atleast one search criteria')
+ end
+ when 'conversations'
+ error('Provide statuses') unless input['statusId'].present?
+ end
+ end,
+
+ format_request_payload: lambda do |input|
+ case input['object_name']
+ when 'contacts'
+ if input['externalID'].present?
+ {
+ 'idType' => 'external',
+ 'id' => input['externalID'],
+ 'email' => input['email'],
+ 'limit' => input['limit']
+ }
+ else
+ {
+ 'email' => input['email'],
+ 'limit' => input['limit']
+ }
+ end
+ when 'conversations'
+ {
+ 'limit' => input['limit'],
+ 'next' => input['next']
+ }
+ when 'meetings'
+ {
+ 'max_start_time' => input['max_start_time'].to_i * 1000,
+ 'min_start_time' => input['min_start_time'].to_i * 1000
+ }
+ else
+ input
+ end
+ end,
+
+ search_object_output: lambda do |input|
+ case input[:object_name]
+ when 'contacts'
+ call('get_contact_schema', '')
+ when 'users'
+ call('get_user_schema', '')
+ when 'accounts'
+ call('get_account_schema', '')
+ when 'conversations'
+ call('get_conversation_schema', '')
+ when 'playbooks'
+ call('get_playbook_schema', '')
+ when 'meetings'
+ call('get_meeting_schema', '')
+ else
+ []
+ end
+ end,
+
+ search_url: lambda do |input|
+ case input['object_name']
+ when 'users', 'playbooks'
+ "/#{input['object_name']}/list"
+ when 'meetings'
+ "/users/#{input['object_name']}/org"
+ when 'conversations'
+ status_id = input['statusId']&.to_s&.split(',')&.smart_join('&statusId=')
+ "/#{input['object_name']}/list?statusId=#{status_id}"
+ else
+ "/#{input['object_name']}"
+ end
+ end,
+
+ sample_search_record: lambda do |input|
+ response = get(call('sample_search_url', input))
+ result = if input['object_name'] == 'playbooks'
+ response&.compact
+ elsif input['object_name'] == 'accounts'
+ response.dig('data', 'accounts')
+ else
+ response['data']
+ end
+ call('format_unix_to_utc_time', result)
+ end,
+
+ sample_search_url: lambda do |input|
+ case input['object_name']
+ when 'users', 'playbooks'
+ "/#{input['object_name']}/list"
+ when 'conversations'
+ "/#{input['object_name']}/list?limit=1"
+ when 'meetings'
+ "/users/#{input['object_name']}/org?" \
+ 'min_start_time=0&max_start_time=15000000000000'
+ when 'accounts'
+ "/#{input['object_name']}?size=1"
+ when 'contacts'
+ if input['externalID'].present?
+ "/#{input['object_name']}?idType=external&id=#{input['externalID']}" \
+ "&limit=1&email=#{input['email']}"
+ else
+ "/#{input['object_name']}?limit=1&email=#{input['email']}"
+ end
+ else
+ "/#{input['object_name']}"
+ end
+ end,
+
+ sample_get_record: lambda do |input|
+ response = get(call('sample_get_url', input))
+ result = case input['object_name']
+ when 'accounts'
+ response.dig('data', 'accounts', 0)
+ when 'messages', 'new_message', 'new_command_message',
+ 'button_clicked'
+ response.dig('data', 'messages', 0)
+ when 'conversation_participant_added',
+ 'conversation_participant_removed'
+ {
+ conversationId: '1485843811',
+ addedParticipants: '243266',
+ changedAt: '2020-01-07T09:41:23.000+00:00'
+ }
+ when 'phone_captured'
+ {
+ conversationId: '1485843811',
+ authorId: '1883929',
+ authorType: 'contact',
+ phoneNumber: '555-444-6666'
+ }
+ when 'conversation_inactive'
+ {
+ conversationId: '1485843811',
+ lastUpdated: '2020-01-07T09:41:23.000+00:00'
+ }
+ when 'contact_updated'
+ {
+ 'endUserId' => '13326063',
+ 'timestamp' => '1575264348541',
+ 'diff': [
+ {
+ 'name' => 'attributes.alias',
+ 'previous' => 'null',
+ 'updated' => 'nadine'
+ }
+ ]
+ }
+ when 'user_availability_updated'
+ {
+ userId: '1894929',
+ updatedAt: '2020-01-07T09:41:23.000+00:00',
+ availability: 'ONLINE'
+ }
+ when 'playbook_goal_met'
+ {
+ conversationId: '1485843811',
+ playbookId: '1561966',
+ contactId: '4213856715',
+ goalId: '42fe19bf-b3f4-44c6-b358-1934dc69a614',
+ goalName: 'Goal name',
+ playbookName: 'New welcome message',
+ contactEmail: 'test@test.com'
+ }
+ when 'contacts', 'user_unsubscribed', 'contact_identified'
+ {
+ 'attributes' => {
+ 'email' => 'swebel@drift.com',
+ 'name' => 'Stephen Webel',
+ 'externalId' => '12341243'
+ },
+ 'createdAt' => '1575264348541',
+ 'id' => '349999553'
+ }
+ when 'timeline'
+ {
+ event: 'test event from workato',
+ externalId: '13326063',
+ createdAt: '2020-01-09T16:00:00.000000+00:00',
+ contactId: '4489629008'
+ }
+ when 'meeting_updated', 'new_meeting'
+ [{ eventType: 'canceled/rescheduled',
+ conversationId: '1485843811' }].concat([response.dig('data', 0)]).inject(:merge)
+ else
+ response.dig('data', 0)
+ end
+ call('format_unix_to_utc_time', result)
+ end,
+
+ sample_get_url: lambda do |input|
+ case input['object_name']
+ when 'users'
+ "/#{input['object_name']}/list"
+ when 'conversations', 'new_conversation'
+ '/conversations/list?limit=1'
+ when 'accounts'
+ "/#{input['object_name']}?size=1"
+ when 'meetings', 'new_meeting', 'meeting_updated'
+ '/users/meetings/org?' \
+ 'min_start_time=0&max_start_time=15000000000000'
+ when 'messages', 'new_message', 'new_command_message', 'button_clicked'
+ conversation_id = call('sample_get_record',
+ 'object_name' => 'conversations')&.dig('id')
+ "/conversations/#{conversation_id}/messages"
+ else
+ "/#{input['object_name']}"
+ end
+ end
+
+ },
+
+ object_definitions: {
+ event: {
+ fields: lambda { |_connection, config_fields|
+ case config_fields['type']
+ when 'contact_identified'
+ call('get_contact_schema')
+ when 'new_message'
+ call('get_message_schema')
+ when 'new_conversation'
+ call('get_conversation_schema')
+ when 'conversation_status_updated'
+ [
+ { name: 'orgId', type: 'integer', label: 'Organization ID' },
+ { name: 'conversationId', type: 'integer' },
+ { name: 'status' },
+ { name: 'changedAt', type: 'integer' }
+ ]
+ when 'conversation_participant_added'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'addedParticipants', type: 'array', of: 'integer' },
+ { name: 'changedAt', type: 'integer' }
+ ]
+ when 'conversation_participant_removed'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'removedParticipants', type: 'array', of: 'integer' },
+ { name: 'changedAt', type: 'integer' }
+ ]
+ when 'button_action'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'author', type: 'object', properties: [
+ { name: 'type' },
+ { name: 'id', type: 'integer' }
+ ] },
+ { name: 'sourceMessageId', type: 'integer' },
+ { name: 'button', type: 'object' }
+ ]
+ when 'playbook_goal_met'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'playbookId', type: 'integer' },
+ { name: 'contactId', type: 'integer' },
+ { name: 'goalId' },
+ { name: 'goalName' },
+ { name: 'playbookName' },
+ { name: 'contactEmail' }
+ ]
+ when 'user_unsubscribe'
+ call('get_contact_schema')
+ else
+ []
+ end
+ }
+ },
+
+ contact: {
+ fields: lambda { |_connection, _config|
+ call('get_contact_schema')
+ }
+ },
+
+ contact_tag: {
+ fields: lambda { |_connection, _config|
+ call('get_contact_tag_schema')
+ }
+ },
+
+ message: {
+ fields: lambda { |_connection, _config|
+ call('get_message_schema')
+ }
+ },
+
+ conversation: {
+ fields: lambda { |_connection, _config|
+ call('get_conversation_schema')
+ }
+ },
+
+ user: {
+ fields: lambda { |_connection, _config|
+ call('get_user_schema')
+ }
+ },
+
+ message_button: {
+ fields: lambda {
+ [
+ { name: 'label' },
+ { name: 'value' },
+ { name: 'type', control_type: 'select', pick_list: 'button_types' },
+ { name: 'style', control_type: 'select', pick_list: 'button_styles' },
+ {
+ name: 'reaction', type: 'object',
+ optional: true,
+ properties: [
+ { name: 'type', control_type: 'select',
+ pick_list: 'reaction_types' },
+ { name: 'message', type: 'string' }
+ ]
+ }
+ ]
+ }
+ },
+
+ custom_action_input: {
+ fields: lambda do |connection, config_fields|
+ input_schema = parse_json(config_fields.dig('input', 'schema') || '[]')
+
+ [
+ {
+ name: 'path',
+ optional: false,
+ hint:
+ if (host = connection['environment']).present?
+ "Base URI is https://#{host}.zuora.com - " \
+ 'path will be appended to this URI. Use absolute URI to ' \
+ 'override this base URI.'
+ end
+ },
+ (
+ if %w[get delete].include?(config_fields['verb'])
+ {
+ name: 'input',
+ type: 'object',
+ control_type: 'form-schema-builder',
+ sticky: input_schema.blank?,
+ label: 'URL parameters',
+ add_field_label: 'Add URL parameter',
+ properties: [
+ {
+ name: 'schema',
+ extends_schema: true,
+ sticky: input_schema.blank?
+ },
+ (
+ if input_schema.present?
+ {
+ name: 'data',
+ type: 'object',
+ properties: call('make_schema_builder_fields_sticky',
+ input_schema)
+ }
+ end
+ )
+ ].compact
+ }
+ else
+ {
+ name: 'input',
+ type: 'object',
+ properties: [
+ {
+ name: 'schema',
+ extends_schema: true,
+ schema_neutral: true,
+ control_type: 'schema-designer',
+ sample_data_type: 'json_input',
+ sticky: input_schema.blank?,
+ label: 'Request body parameters',
+ add_field_label: 'Add request body parameter'
+ },
+ (
+ if input_schema.present?
+ {
+ name: 'data',
+ type: 'object',
+ properties:
+ input_schema.each { |field| field[:sticky] = true }
+ }
+ end
+ )
+ ].compact
+ }
+ end
+ ),
+ {
+ name: 'output',
+ control_type: 'schema-designer',
+ sample_data_type: 'json_http',
+ extends_schema: true,
+ schema_neutral: true,
+ sticky: true
+ }
+ ]
+ end
+ },
+
+ custom_action_output: {
+ fields: lambda do |_connection, config_fields|
+ parse_json(config_fields['output'] || '[]')
+ end
+ },
+
+ get_object: {
+ fields: lambda do |_connection, config_fields|
+ [{ name: 'id', type: 'integer',
+ sticky: true,
+ label: "#{config_fields['object_name'] || 'record'} ID" }]
+ end
+ },
+
+ object_delete: {
+ fields: lambda do |_connection, config_fields|
+ object_name =
+ {
+ 'contacts' => 'Contact',
+ 'accounts' => 'Account'
+ }[config_fields['object_name']]
+ [
+ { name: 'id',
+ type: 'integer',
+ label: "#{object_name || 'Record'} ID",
+ optional: false }
+ ]
+ end
+ },
+
+ object_create: {
+ fields: lambda do |_connection, config_fields|
+ call("create_#{config_fields['object_name']}_schema", '')
+ end
+ },
+
+ object_update: {
+ fields: lambda do |_connection, config_fields|
+ call("update_#{config_fields['object_name']}_schema", '')
+ end
+ },
+
+ object_search: {
+ fields: lambda do |_connection, config_fields|
+ case config_fields['object_name']
+ when 'contacts'
+ call('search_contacts_schema', '')
+ when 'conversations'
+ call('search_conversations_schema', '')
+ when 'accounts'
+ call('search_accounts_schema', '')
+ when 'meetings'
+ call('search_meetings_schema', '')
+ else
+ []
+ end
+ end
+ },
+
+ object_get: {
+ fields: lambda do |_connection, config_fields|
+ object_name =
+ {
+ 'users' => 'User',
+ 'conversations' => 'Conversation',
+ 'contacts' => 'Contact',
+ 'accounts' => 'Account'
+ }[config_fields['object_name']]
+ [
+ { name: 'id',
+ type: 'integer',
+ label: "#{object_name || 'Record'} ID",
+ optional: false }
+ ]
+ end
+ },
+
+ object_output: {
+ fields: lambda do |_connection, config_fields|
+ case config_fields['object_name']
+ when 'contact_identified', 'contacts', 'user_unsubscribed'
+ call('get_contact_schema', '')
+ when 'new_message', 'new_command_message', 'messages', 'button_clicked'
+ call('get_message_schema', '')
+ when 'new_conversation', 'conversations'
+ call('get_conversation_schema', '')
+ when 'conversation_inactive'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'lastUpdated', type: 'integer' }
+ ]
+ when 'conversation_participant_added'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'addedParticipants', type: 'array', of: 'integer' },
+ { name: 'changedAt', type: 'integer' }
+ ]
+ when 'conversation_participant_removed'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'removedParticipants', type: 'array', of: 'integer' },
+ { name: 'changedAt', type: 'integer' }
+ ]
+ when 'button_action'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'author', type: 'object', properties: [
+ { name: 'type' },
+ { name: 'id', type: 'integer' }
+ ] },
+ { name: 'sourceMessageId', type: 'integer' },
+ { name: 'button', type: 'object' }
+ ]
+ when 'playbook_goal_met'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'playbookId', type: 'integer' },
+ { name: 'contactId', type: 'integer' },
+ { name: 'goalId' },
+ { name: 'goalName' },
+ { name: 'playbookName' },
+ { name: 'contactEmail' }
+ ]
+ when 'user_unsubscribe', 'contact'
+ call('get_contact_schema', '')
+ when 'users'
+ call('get_user_schema', '')
+ when 'accounts'
+ call('get_account_schema', '')
+ when 'new_meeting'
+ call('get_meeting_schema', '').ignored('eventType', 'status', 'meetingSource', 'updatedAt', 'meetingType',
+ 'endUserTimeZone', 'meetingNotes', 'bookedBy')
+ when 'meeting_updated'
+ call('get_meeting_schema', '').ignored('status', 'meetingSource', 'updatedAt', 'meetingType',
+ 'endUserTimeZone', 'meetingNotes', 'bookedBy')
+ when 'user_availability_updated'
+ [
+ { name: 'userId', type: 'integer' },
+ { name: 'updatedAt', type: 'integer', hint: 'time of update' },
+ { name: 'availability' }
+ ]
+ when 'phone_captured'
+ [
+ { name: 'conversationId', type: 'integer' },
+ { name: 'authorId', type: 'integer' },
+ { name: 'authorType' },
+ { name: 'phoneNumber' }
+ ]
+ when 'contact_updated'
+ [
+ { name: 'endUserId', type: 'integer' },
+ { name: 'timestamp', type: 'date_time' },
+ { name: 'orgId', type: 'integer' },
+ { name: 'diff', type: 'array', of: 'object',
+ properties: [
+ { name: 'name' },
+ { name: 'previous' },
+ { name: 'updated' }
+ ] }
+ ]
+ when 'timeline'
+ call('get_timeline_schema', '')
+ else
+ []
+ end
+ end
+ },
+
+ object_search_output: {
+ fields: lambda do |_connection, config_fields|
+ [
+ { name: 'records', label: config_fields['object_name'].labelize || 'records',
+ type: 'array', of: 'object',
+ properties: call('search_object_output', config_fields) }
+ ]
+ end
+ }
+
+ },
+
+ actions: {
+ custom_action: {
+ description: "Custom action " \
+ "in Drift",
+ help: 'Build your own Drift action with a HTTP request.' \
+ "
Drift API Documentation ",
+
+ config_fields: [{
+ name: 'verb',
+ label: 'Request type',
+ hint: 'Select HTTP method of the request',
+ optional: false,
+ control_type: 'select',
+ pick_list: %w[get post put delete].map { |v| [v.upcase, v] }
+ }],
+
+ input_fields: lambda do |object_definitions|
+ object_definitions['custom_action_input']
+ end,
+
+ execute: lambda do |_connection, input|
+ verb = input['verb']
+ if %w[get post put patch delete].exclude?(verb)
+ error("#{verb} not supported")
+ end
+ data = input.dig('input', 'data').presence || {}
+
+ case verb
+ when 'get'
+ get(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ when 'post'
+ post(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ when 'put'
+ put(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ when 'delete'
+ delete(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ end
+ end,
+
+ output_fields: lambda do |object_definitions|
+ object_definitions['custom_action_output']
+ end
+ },
+
+ search_records: {
+ description: lambda do |_input, pick_lists|
+ "Search #{pick_lists['object_name']&.downcase || 'records'}" \
+ " in Drift"\
+ end,
+ help: lambda do |_input, pick_lists|
+ help = { 'Contacts' => "Note that email is not a primary identifier, and it's possible that multiple" \
+ ' contacts have the same email - so querying on email returns a list.',
+ 'Users' => 'Search will return the full list of users (with the full user model metadata).',
+ 'Conversations' => 'Conversations returned will be ordered by their updatedAt time with ' \
+ 'the most recently updated at the top of the list. Common conversation updates' \
+ ' include status updates (i.e. closing the conversation) or a post of a ' \
+ 'new message to that conversation.',
+ 'Accounts' => 'Note: The next field in the output may not always be present. ' \
+ 'Once the accounts are exhausted, the next field will be absent' \
+ ' from the response body.',
+ 'Bot playbooks' => 'Bot playbooks are currently available on Pro and higher plans.',
+ 'Booked meetings' => 'The action returns the booked meetings for an account, across all the Drift ' \
+ 'agents, in a given time range up to a max of 1000 meetings per request,' \
+ ' and up to 30 days in the past.' }[pick_lists['object_name']] || ''
+
+ { body: help.present? ? help : '' }
+ end,
+ config_fields: [
+ { name: 'object_name', control_type: 'select',
+ hint: 'Please select the Drift object.',
+ pick_list: 'search_object_list',
+ label: 'Object name',
+ optional: false }
+ ],
+ input_fields: lambda do |object_definitions|
+ object_definitions['object_search']
+ end,
+ execute: lambda do |_connection, input|
+ call('validate_search_criteria', input)
+ payload = call('format_request_payload', input).compact
+ response = get(call('search_url', input), payload.except('object_name')).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ result = if input['object_name'] == 'playbooks'
+ response&.compact
+ elsif input['object_name'] == 'accounts'
+ response.dig('data', 'accounts')
+ else
+ response['data']
+ end
+ records = call('format_unix_to_utc_time', result) || []
+ { records: records }
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['object_search_output']
+ end,
+ sample_output: lambda do |_connection, input|
+ { records: call('sample_search_record', input) || [] }
+ end
+ },
+
+ get_record: {
+ description: lambda do |_input, pick_lists|
+ "Get #{pick_lists['object_name']&.
+ downcase || 'object'} by ID in "\
+ 'Drift'
+ end,
+ help: {
+ body: 'Retrieve information about a record associated ' \
+ 'with the given record ID in Drift.'
+ },
+ config_fields: [
+ { name: 'object_name', control_type: 'select',
+ hint: 'Please select the Drift object from the list.',
+ pick_list: 'get_object_list',
+ label: 'Object name',
+ optional: false }
+ ],
+ input_fields: lambda do |object_definitions|
+ object_definitions['object_get']
+ end,
+ execute: lambda do |_connection, input|
+ response = get("#{input['object_name']}/#{input['id']}").
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end['data']
+ call('format_unix_to_utc_time', response)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['object_output']
+ end,
+ sample_output: lambda do |_connection, input|
+ call('sample_get_record', input) || []
+ end
+ },
+
+ create_record: {
+ description: lambda do |_input, pick_lists|
+ "Create #{pick_lists['object_name']&.
+ downcase || 'record'} in "\
+ 'Drift'
+ end,
+ help: lambda do |_input, pick_lists|
+ help = { 'Contact' => 'When creating a contact, the only field that is required is email. ' \
+ 'If the contact is already present, the request will respond with an error.',
+ 'Conversation' => 'Note that the Integration source is a special attribute in the ' \
+ 'message and will appear in the header of the newly started conversation. ' \
+ "We recommend including this in each request.
We'll attempt to find " \
+ 'a contact in your account with the provided email, based on first created, and ' \
+ "open up a new conversation in drift with them. If a contact with that email isn't " \
+ 'found, then one will be created.',
+ 'Account' => 'Note that accounts must have a unique domain. Accounts are split by ' \
+ 'domain as a unique identifier - attempting to create an account with an identical domain ' \
+ 'will result in an error.',
+ 'Message' => 'The message API renders the body as HTML. Tags like <a>, <p>, ' \
+ '<b>, etc. should all work.
When posting a youtube link in the chat, ' \
+ 'you should post the link as an <a> tag.
Example body message: ' \
+ "Hey there, check this out here ",
+ 'Timeline event' => 'To use an External ID for contact lookup and posting, provide ' \
+ 'your External ID value in the payload and leave the Contact ID field empty. ' \
+ 'The timeline event should post in the same way if there is a contact match to your ' \
+ 'external ID.' }[pick_lists['object_name']] || ''
+
+ { body: help.present? ? help : '' }
+ end,
+ config_fields: [
+ { name: 'object_name', control_type: 'select',
+ hint: 'Please select the Drift object.',
+ pick_list: 'create_object_list',
+ label: 'Object name',
+ optional: false }
+ ],
+ input_fields: lambda do |object_definitions|
+ object_definitions['object_create']
+ end,
+ execute: lambda do |_connection, input|
+ object_name = input.delete('object_name')
+ url = case object_name
+ when 'messages'
+ "/conversations/#{input.delete('conversationId')}/messages"
+ when 'conversations'
+ '/conversations/new'
+ when 'timeline'
+ "/contacts/#{object_name}"
+ when 'accounts'
+ '/accounts/create'
+ else
+ "/#{object_name}"
+ end
+
+ payload = case object_name
+ when 'contacts'
+ { attributes: call('format_date_time_to_unix', input) }
+ else
+ call('format_date_time_to_unix', input)
+ end
+ response =
+ post(url).
+ payload(payload).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+
+ case object_name
+ when 'conversations'
+ result = parse_json(response.to_json)
+ call('format_unix_to_utc_time', result)
+ else
+ call('format_unix_to_utc_time', response['data'])
+ end
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['object_output']
+ end,
+ sample_output: lambda do |_connection, input|
+ call('sample_get_record', input) || []
+ end
+ },
+
+ update_record: {
+ description: lambda do |_input, pick_lists|
+ "Update #{pick_lists['object_name']&.
+ downcase || 'record'} in "\
+ 'Drift'
+ end,
+ help: lambda do |_input, pick_lists|
+ help = { 'Contact' => 'You can update a contact record in Drift by providing the contact ID of ' \
+ 'the desired contact in Drift, with the appropriate properties to update in the request.',
+ 'Account' => 'While updating or changing the existing type of a field is allowed, ' \
+ 'it is not recommended. Drift uses the type of the account field to allow segmentation ' \
+ 'and filtering in the Accounts UI of the Drift platform. Using/changing different types ' \
+ 'may break this behavior.',
+ 'User' => 'Update a user record in Drift by providing the ' \
+ 'user ID.' }[pick_lists['object_name']] || ''
+
+ { body: help.present? ? help : '' }
+ end,
+ config_fields: [
+ { name: 'object_name', control_type: 'select',
+ hint: 'Please select the Drift object.',
+ pick_list: 'update_object_list',
+ label: 'Object name',
+ optional: false }
+ ],
+ input_fields: lambda do |object_definitions|
+ object_definitions['object_update']
+ end,
+ execute: lambda do |_connection, input|
+ object_name = input.delete('object_name')
+ url = case object_name
+ when 'accounts'
+ "/#{object_name}/update"
+ when 'contacts'
+ "/#{object_name}/#{input.delete('id')}"
+ when 'users'
+ "/#{object_name}/update?userId=#{input.delete('id')}"
+ end
+ payload = call('format_date_time_to_unix', input)
+ response =
+ patch(url).
+ payload(payload).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end['data']
+ call('format_unix_to_utc_time', response)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['object_output']
+ end,
+ sample_output: lambda do |_connection, input|
+ call('sample_get_record', input) || []
+ end
+ },
+
+ delete_record: {
+ subtitle: 'Remove a record',
+ description: lambda do |_input, pick_lists|
+ "Delete #{pick_lists['object_name']&.
+ downcase || 'record'} in "\
+ 'Drift'
+ end,
+ help: lambda do |_input, pick_lists|
+ help = { 'Contact' => 'You can update a contact record in Drift by providing the contact ID of ' \
+ 'the desired contact in Drift, with the appropriate properties to update in the request.',
+ 'Account' => 'Note that deletes are never true deletes from the Drift system. ' \
+ 'Deleting an account will hide it from the list view in drift, but you can still ' \
+ 'query the account by ID later on. In this case, the account will return in the ' \
+ 'response with a deleted: true field.' }[pick_lists['object_name']] || ''
+
+ { body: help.present? ? help : '' }
+ end,
+ config_fields: [
+ { name: 'object_name', control_type: 'select',
+ hint: 'Please select the Drift object.',
+ pick_list: 'delete_object_list',
+ label: 'Object name',
+ optional: false }
+ ],
+ input_fields: lambda do |object_definitions|
+ object_definitions['object_delete']
+ end,
+ execute: lambda do |_connection, input|
+ delete("/#{input['object_name']}/#{input['id']}").
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ end,
+ output_fields: lambda do |_object_definitions|
+ [{ name: 'result' }, { name: 'ok', type: 'boolean' }]
+ end,
+ sample_output: lambda do |_connection, _input|
+ { 'result': 'OK', 'ok': true }
+ end
+ }
+ },
+
+ webhook_keys: lambda { |_params, _headers, payload|
+ "#{payload['type']}{payload['token']}"
+ },
+
+ triggers: {
+ new_object: {
+ title: 'New object',
+ subtitle: 'Triggers when an object created in Drift',
+ description: lambda do |_connection, new_object_list|
+ "New #{new_object_list[:object_name]&.downcase || 'object'} " \
+ " in Drift"
+ end,
+ help: lambda do |_connection, new_object_list|
+ help = {
+ 'Contact' => 'Triggers when a contact added their email in chat.',
+ 'Message' => 'Triggers when a new message was created.',
+ 'Command message' => 'Triggers when a new command message was received. Currently ' \
+ 'these begin with a slash / - ex: /msg. This is a filtered, lower access, version of ' \
+ 'new_message. Recommended if your app only cares about a trigger phrase being sent in chat.',
+ 'Conversation' => 'Triggers when a new conversation was started. This explicitly looks' \
+ ' at conversations not started by bots - i.e. a site visitor interaction with a welcome message.',
+ 'Conversation participant added' => 'Triggers when participants were added to a conversation.',
+ 'Meeting' =>
+ 'Triggers when a meeting was booked by a contact or site visitor with a member of your team -' \
+ ' scheduled by the bot or via your calendar schedule page. Requires calendar connection in drift.',
+ 'Phone' => 'Triggers when a phone number was captured in a conversation.',
+ 'Button click' => 'Triggers when a button was clicked in a conversation.' \
+ ' The payload will include the contact ID.'
+ }[new_object_list[:object_name]] || ''
+
+ { body: help.present? ? help : "Configure Drift to send events to Workato." \
+ '
To find your static webhook URI, click on Tools → ' \
+ 'Connector SDK → Drift → Settings then click on Copy static webhook URI.',
+ learn_more_url: 'https://devdocs.drift.com/docs/webhook-events-1',
+ learn_more_text: 'Drift webhooks documentation' }
+ end,
+ config_fields: [
+ { name: 'object_name', control_type: 'select',
+ pick_list: 'new_object_list',
+ hint: 'Please select object from the list',
+ label: 'Object name',
+ optional: false }
+ ],
+ webhook_key: lambda { |connection, input|
+ "#{input['object_name']}{connection['verification_token']}"
+ },
+ webhook_notification: lambda { |_input, payload|
+ object = { 'type' => payload['type'], 'orgId' => payload['orgId'] }.
+ merge(payload['data'])
+ date_fields = %w[start_date last_active last_contacted scheduledAt slotStart slotEnd updatedAt
+ indexedAt createdAt createDateTime updateDateTime]
+ object.map do |key, value|
+ if date_fields.include?(key)
+ { key => (value.to_i / 1000).to_i.to_time.utc }
+ elsif key == 'attributes'
+ val = value.map do |k, v|
+ if date_fields.include?(k)
+ { k => (v.to_i / 1000).to_i.to_time.utc }
+ else
+ { k => v }
+ end
+ end.inject(:merge)
+ { key => val }
+ else
+ { key => value }
+ end
+ end.inject(:merge)
+ },
+ dedup: lambda do |_|
+ Time.now.to_f
+ end,
+ output_fields: lambda { |object_definitions|
+ [
+ { name: 'orgId', type: 'string', label: 'Organization ID' },
+ { name: 'type', type: 'string', label: 'Event type' }
+ ].concat(object_definitions['object_output']).compact
+ },
+ sample_output: lambda do |_connection, input|
+ sample_output = [{ orgId: '1351205',
+ type: 'Event' }].
+ concat([call('sample_get_record', input)] || [])
+ sample_output.inject(:merge)
+ end
+
+ },
+ update_object: {
+ title: 'Updated/deleted object ',
+ subtitle: 'Triggers when an object is updated, removed, unsubscribed in Drift',
+ description: lambda do |_connection, new_updated_object_list|
+ "Updated/removed " \
+ "#{new_updated_object_list[:object_name]&.downcase || 'object'} " \
+ " in Drift"
+ end,
+ help: lambda do |_connection, new_updated_object_list|
+ help = {
+ 'Conversation status' => 'Triggers when a conversation has not seen activity in the previous 10 minutes.',
+ 'Meeting' => 'Triggers when a previously booked meeting was either rescheduled or canceled.',
+ 'Contact' => 'Triggers when a contact record has been updated in Drift.',
+ 'User availability' => 'Triggers when a drift user\'s availability has been changed.',
+ 'Conversation participant removed' => 'Triggers when participants were removed from a conversation.',
+ 'User unsubscribed' => 'Trigger when a user unsubscribed from all emails - this is invoked whenever' \
+ ' someone opts out of email communication (either manually, or by the api).',
+ 'Playbook goal met' =>
+ 'Trigger will fire whenever a goal is met with a previously identified' \
+ ' contact (i.e. a site visitor that provided their email or is known in the' \
+ ' Drift UI). The payload will include the contact ID.'
+ }[new_updated_object_list[:object_name]] || ''
+
+ { body: help.present? ? help : "Configure Drift to send events to Workato." \
+ '
To find your static webhook URI, click on Tools → ' \
+ 'Connector SDK → Drift → Settings then click on Copy static webhook URI.',
+ learn_more_url: 'https://devdocs.drift.com/docs/webhook-events-1',
+ learn_more_text: 'Drift webhooks documentation' }
+ end,
+ config_fields: [
+ { name: 'object_name', control_type: 'select',
+ pick_list: 'new_updated_object_list',
+ hint: 'Please select object from the list',
+ label: 'Object name',
+ optional: false }
+ ],
+ webhook_key: lambda { |connection, input|
+ "#{input['object_name']}{connection['verification_token']}"
+ },
+ webhook_notification: lambda { |_connection, payload|
+ object = { 'type' => payload['type'], 'orgId' => payload['orgId'] }.
+ merge(payload['data'])
+ date_fields = %w[start_date last_active last_contacted scheduledAt slotStart lastUpdated
+ timestamp slotEnd updatedAt indexedAt createdAt createDateTime updateDateTime]
+ object.map do |key, value|
+ if date_fields.include?(key)
+ { key => (value.to_i / 1000).to_i.to_time.utc }
+ elsif key == 'attributes'
+ val = value.map do |k, v|
+ if date_fields.include?(k)
+ { k => (v.to_i / 1000).to_i.to_time.utc }
+ else
+ { k => v }
+ end
+ end.inject(:merge)
+ { key => val }
+ else
+ { key => value }
+ end
+ end.inject(:merge)
+ },
+ dedup: lambda do |_|
+ Time.now.to_f
+ end,
+ output_fields: lambda { |object_definitions|
+ [
+ { name: 'orgId', type: 'string', label: 'Organization ID' },
+ { name: 'type', type: 'string', label: 'Event type' }
+ ].concat(object_definitions['object_output']).compact
+ },
+ sample_output: lambda do |_connection, input|
+ sample_output = [{ orgId: '1351205',
+ type: 'Event' }].
+ concat([call('sample_get_record', input)] || [])
+ sample_output.inject(:merge)
+ end
+
+ }
+ },
+
+ pick_lists: {
+ new_object_list: lambda do |_connection|
+ [
+ %w[Contact contact_identified],
+ %w[Message new_message],
+ %w[Command\ message new_command_message],
+ %w[Conversation new_conversation],
+ %w[Conversation\ participant\ added conversation_participant_added],
+ %w[Meeting new_meeting],
+ %w[Phone phone_captured],
+ %w[Button\ click button_clicked]
+ ]
+ end,
+
+ new_updated_object_list: lambda do |_connection|
+ [
+ %w[Conversation\ status conversation_inactive],
+ %w[Meeting meeting_updated],
+ %w[Contact contact_updated],
+ %w[User\ availability user_availability_updated],
+ %w[Conversation\ participant\ removed conversation_participant_removed],
+ %w[User\ unsubscribed user_unsubscribed],
+ %w[Playbook\ goal\ met playbook_goal_met]
+ ]
+ end,
+
+ search_object_list: lambda do
+ [
+ %w[Contacts contacts],
+ %w[Users users],
+ %w[Conversations conversations],
+ %w[Accounts accounts],
+ %w[Bot\ playbooks playbooks],
+ %w[Booked\ meetings meetings]
+ ]
+ end,
+
+ get_object_list: lambda do
+ [
+ %w[Contact contacts],
+ %w[User users],
+ %w[Conversation conversations],
+ %w[Account accounts]
+ ]
+ end,
+
+ create_object_list: lambda do
+ [
+ %w[Contact contacts],
+ %w[Conversation conversations],
+ %w[Message messages],
+ %w[Account accounts],
+ %w[Timeline\ event timeline]
+ ]
+ end,
+
+ update_object_list: lambda do
+ [
+ %w[Contact contacts],
+ %w[User users],
+ %w[Account accounts]
+ ]
+ end,
+
+ delete_object_list: lambda do
+ [
+ %w[Contact contacts],
+ %w[Account accounts]
+ ]
+ end,
+
+ status_list: lambda do
+ [
+ %w[Open OPEN],
+ %w[Closed CLOSED],
+ %w[Pending PENDING]
+ ]
+ end,
+
+ status_id_list: lambda do
+ [
+ %w[Open 1],
+ %w[Closed 2],
+ %w[Pending 3]
+ ]
+ end,
+
+ event_types: lambda do |_connection|
+ [
+ ['Conversation status updated', 'conversation_status_updated'],
+ ['Conversation participant added', 'conversation_participant_added'],
+ ['Conversation participant removed',
+ 'conversation_participant_removed'],
+ ['Button action', 'button_action'],
+ ['Playbook goal met', 'playbook_goal_met'],
+ ['User unsubscribed', 'user_unsubscribe']
+ ]
+ end,
+
+ message_types: lambda do |_connection|
+ [
+ %w[Chat chat],
+ ['Private note', 'private_note'],
+ ['Private prompt', 'private_prompt'],
+ %w[Edit edit]
+ ]
+ end,
+ edit_types: lambda do |_input|
+ [
+ %w[Delete delete],
+ %w[Replace replace],
+ ['Replace body', 'replace_body'],
+ ['Replace buttons', 'replace_buttons']
+ ]
+ end,
+ reaction_types: lambda do |_input|
+ [
+ %w[Delete delete],
+ %w[Replace replace]
+ ]
+ end,
+ button_types: lambda do |_input|
+ [
+ %w[Reply reply],
+ %w[Compose compose],
+ %w[Action action]
+ ]
+ end,
+ button_styles: lambda do |_input|
+ [
+ %w[Primary primary],
+ %w[Danger danger]
+ ]
+ end,
+ data_types: lambda do |_input|
+ [
+ %w[String STRING],
+ %w[Email EMAIL],
+ %w[Number NUMBER],
+ %w[Team\ member TEAMMEMBER],
+ %w[Enum ENUM],
+ %w[Date DATE],
+ %w[Date\ time DATETIME],
+ %w[Latitude\ Longitude LATLON],
+ %w[Latitude LAT],
+ %w[Longitude LON],
+ %w[Phone PHONE],
+ %w[URL URL],
+ %w[Enum\ array ENUMARRAY]
+ ]
+ end
+ }
+}