diff --git a/custom_connectors/oauth2/plangrid.rb b/custom_connectors/oauth2/plangrid.rb
new file mode 100644
index 00000000..f75579e5
--- /dev/null
+++ b/custom_connectors/oauth2/plangrid.rb
@@ -0,0 +1,2785 @@
+{
+ title: 'Plangrid',
+ connection: {
+ fields: [
+ {
+ name: 'client_id',
+ label: 'Client ID',
+ optional: false,
+ hint: 'To create client id, you need to register an application' \
+ ' under Admin Console => Project => Oauth => Create Oauth app'
+ },
+ {
+ name: 'client_secret',
+ label: 'Client secret',
+ control_type: 'password',
+ optional: false,
+ hint: 'To create client id, you need to register an application' \
+ ' under Admin Console => Project => Oauth => Create Oauth app'
+ }
+ ],
+ authorization: {
+ type: 'oauth2',
+ authorization_url: lambda do |connection|
+ 'https://io.plangrid.com/oauth/authorize?response_type=' \
+ "code&client_id=#{connection['client_id']}&" \
+ 'scope=write:projects%20read:profile'
+ end,
+ acquire: lambda do |connection, auth_code, redirect_uri|
+ response = post('https://io.plangrid.com/oauth/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
+ [response, nil, nil]
+ end,
+ refresh_on: [401, 403],
+ refresh: lambda do |_connection, refresh_token|
+ post('https://io.plangrid.com/oauth/token').
+ payload(grant_type: 'refresh_token',
+ refresh_token: refresh_token).
+ request_format_www_form_urlencoded
+ end,
+ apply: lambda do |_connection, access_token|
+ if current_url.include?('https://io.plangrid.com')
+ headers(Authorization: "Bearer #{access_token}",
+ Accept: 'application/vnd.plangrid+json; version=1')
+ end
+ end
+ },
+ base_uri: lambda do |_connection|
+ 'https://io.plangrid.com'
+ end
+ },
+ test: ->(_connection) { get('/me') },
+ object_definitions: {
+ project: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'name', label: 'Project name', sticky: true },
+ { name: 'custom_id', label: 'Project code', sticky: true },
+ { name: 'organization_id' },
+ { name: 'type', control_type: 'select',
+ label: 'Project type', sticky: true,
+ pick_list: 'project_types',
+ toggle_hint: 'Select project type',
+ toggle_field: {
+ name: 'type',
+ label: 'Project type',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Project type with possible values of general,' \
+ ' manufacturing, power, water-sewer-waste, industrial-' \
+ 'petroleum, transportation, hazardous-waste, telecom, ' \
+ 'education-k-12, education-higher, gov-federal, ' \
+ 'gov-state-local, or other'
+ } },
+ { name: 'status', label: 'Project status', sticky: true },
+ { name: 'owner', sticky: true, label: 'Project owner' },
+ { name: 'start_date', type: 'date',
+ sticky: true,
+ render_input: 'date_conversion',
+ parse_output: 'date_conversion',
+ label: 'Project start date',
+ hint: 'Project start date. ISO-8601 date format (YYYY-MM-DD).' },
+ { name: 'end_date', type: 'date',
+ sticky: true,
+ render_input: 'date_conversion',
+ parse_output: 'date_conversion',
+ label: 'Project end date',
+ hint: 'Project end date. ISO-8601 date format (YYYY-MM-DD).' },
+ { name: 'street_1', sticky: true,
+ label: 'Street line 1' },
+ { name: 'street_2', sticky: true, label: 'Street line 2' },
+ { name: 'city', sticky: true, label: 'Town or city' },
+ { name: 'region', sticky: true, label: 'State, province, or region' },
+ { name: 'postal_code', sticky: true, label: 'Zip or postal code' },
+ { name: 'country',
+ sticky: true,
+ hint: 'Project address country in 2-letter ISO 3166 code.' },
+ { name: 'latitude' },
+ { name: 'longitude' },
+ { name: 'updated_at', type: 'date_time',
+ render_input: 'date_time_conversion',
+ parse_output: 'date_time_conversion',
+ label: 'Updated at' },
+ { name: 'add_to_organization', type: 'boolean',
+ control_type: 'checkbox',
+ hint: 'Boolean indicating whether to add the project to the ' \
+ 'organization that the API user belongs to or not. By default ' \
+ 'the project is created as a personal project and not as an ' \
+ 'organization-linked project.',
+ toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'add_to_organization',
+ label: 'Add to organization',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } }
+ ]
+ end
+ },
+ document: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'Document ID' },
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'name', label: 'Document name' },
+ { name: 'folder' },
+ { name: 'url' },
+ { name: 'created_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'created_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'deleted', type: 'boolean' },
+ { name: 'updated_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' }
+ ]
+ end
+ },
+ task: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'Task ID' },
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'assignees', type: 'array', of: 'object', properties: [
+ { name: 'assignee' }
+ ] },
+ { name: 'closed_at', type: 'date_time',
+ render_input: 'date_time_conversion',
+ parse_output: 'date_time_conversion' },
+ { name: 'created_at', type: 'date_time',
+ render_input: 'render_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'created_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'comments', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'cost_impact', type: 'number' },
+ { name: 'has_cost_impact', type: 'boolean',
+ control_type: 'checkbox', toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'cost_impact',
+ label: 'Cost impact',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } },
+ { name: 'currency_code',
+ hint: 'The ISO-4217 currency code of the cost_impact,' \
+ ' Currently only supports USD. maybe null if cost_impact is ' \
+ 'not specified' },
+ { name: 'current_annotation', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'color' },
+ { name: 'stamp' },
+ { name: 'visibility' },
+ { name: 'deleted', type: 'boolean',
+ control_type: 'checkbox', toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } },
+ { name: 'sheet', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' }
+ ] }
+ ] },
+ { name: 'deleted', type: 'boolean',
+ control_type: 'checkbox', toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } },
+ { name: 'description' },
+ { name: 'due_at', type: 'date_time',
+ render_input: 'date_time_conversion',
+ parse_output: 'date_time_conversion' },
+ { name: 'followers', type: 'array', of: 'object', properties: [
+ { name: 'type' },
+ { name: 'uid' }
+ ] },
+ { name: 'issue_list', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' }
+ ] },
+ { name: 'number', type: 'number' },
+ { name: 'photos', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'room' },
+ { name: 'schedule_impact', type: 'integer' },
+ { name: 'has_schedule_impact', type: 'boolean',
+ control_type: 'checkbox', toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'has_schedule_impact',
+ label: 'Has schedule impact',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } },
+ { name: 'start_date',
+ type: 'date_time',
+ render_input: 'date_conversion',
+ parse_output: 'date_conversion' },
+ { name: 'status', control_type: 'select', pick_list:
+ %w[open in_review pending closed].select { |op| [op.labelize, op] },
+ toggle_hint: 'Select status',
+ toggle_field: {
+ name: 'status',
+ label: 'Status',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are : "open", "in_review", "pending",' \
+ ' "closed".'
+ } },
+ { name: 'string',
+ hint: 'One to two character stamp associated with task.' },
+ { name: 'title' },
+ { name: 'type', control_type: 'select',
+ pick_list: [
+ %w[issue issue],
+ %w[Planned\ work planned_work],
+ %w[other other]
+ ],
+ toggle_hint: 'Select type',
+ toggle_field: {
+ name: 'type',
+ label: 'Type',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: "issue", "planned_work",' \
+ ' "other".'
+ } },
+ { name: 'updated_at',
+ render_input: 'date_time_conversion',
+ parse_output: 'date_time_conversion',
+ type: 'date_time' },
+ { name: 'updated_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] }
+
+ ]
+ end
+ },
+ file_upload: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid' },
+ { name: 'aws_post_form_arguments', type: 'object',
+ properties: [
+ { name: 'action' },
+ { name: 'fields', type: 'array', of: 'object', properties: [
+ { name: 'name' },
+ { name: 'value' }
+ ] }
+ ] },
+ { name: 'webhook_url' }
+ ]
+ end
+ },
+ annotation: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'Unique Identifier' },
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'color' },
+ { name: 'stamp' },
+ { name: 'visibility' },
+ { name: 'deleted', type: 'boolean',
+ control_type: 'checkbox', toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } },
+ { name: 'sheet', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'label' },
+ { name: 'color' }
+ ] }
+ ]
+ end
+ },
+ snapshot: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'Snapshot ID' },
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'title' },
+ { name: 'url' },
+ { name: 'created_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'created_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'sheet', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' }
+ ] },
+ { name: 'deleted', type: 'boolean',
+ control_type: 'checkbox', toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } }
+ ]
+ end
+ },
+ photo: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'Photo ID' },
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'title' },
+ { name: 'url' },
+ { name: 'created_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'created_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'deleted', type: 'boolean', control_type: 'checkbox',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } }
+ ]
+ end
+ },
+ field_report: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'File report ID' },
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'title' },
+ { name: 'description' },
+ { name: 'report_date', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'field_report_type', type: 'object', properties: [
+ { name: 'name' },
+ { name: 'project_uid' },
+ { name: 'status' },
+ { name: 'uid' }
+ ] },
+ { name: 'pdf_url' },
+ { name: 'pdf_form_values', type: 'array', of: 'object',
+ properties: [
+ { name: 'name' },
+ { name: 'value' }
+ ] },
+ { name: 'pg_form_values', type: 'array', of: 'object', properties: [
+ { name: 'pg_equipment_entries', type: 'array', of: 'object',
+ properties: [
+ { name: 'uid' },
+ { name: 'timespan' },
+ { name: 'quantity', type: 'integer' },
+ { name: 'item' },
+ { name: 'description' },
+ { name: 'deleted', type: 'boolean',
+ control_type: 'checkbox',
+ toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } }
+ ] },
+ { name: 'pg_materials_entries', type: 'array', of: 'object',
+ properties: [
+ { name: 'uid' },
+ { name: 'unit', type: 'integer' },
+ { name: 'quantity', type: 'integer' },
+ { name: 'item' },
+ { name: 'description' },
+ { name: 'deleted' }
+ ] },
+ { name: 'pg_worklog_entries', type: 'array', of: 'object',
+ properties: [
+ { name: 'uid' },
+ { name: 'trade' },
+ { name: 'timespan' },
+ { name: 'headcount', type: 'integer' },
+ { name: 'description' },
+ { name: 'deleted', type: 'boolean',
+ control_type: 'checkbox',
+ toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } }
+ ] }
+ ] },
+ { name: 'attachments', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'created_by', type: 'object', properties: [
+ { name: 'email' },
+ { name: 'uid' },
+ { name: 'url' }
+ ] },
+ { name: 'photos', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'project_uid', label: 'Project ID' },
+ { name: 'report_date', type: 'date' },
+ { name: 'snapshots', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'status' },
+ { name: 'updated_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'weather', type: 'object', properties: [
+ { name: 'humidity', type: 'number' },
+ { name: 'precipitation_accumulation', type: 'number' },
+ { name: 'precipitation_accumulation_unit' },
+ { name: 'speed_unit' },
+ { name: 'summary_key' },
+ { name: 'temperature_max', type: 'integer' },
+ { name: 'temperature_min' },
+ { name: 'temperature_unit' },
+ { name: 'wind_bearing', type: 'integer' },
+ { name: 'wind_gust', type: 'number' },
+ { name: 'wind_speed', type: 'number' }
+ ] }
+ ]
+ end
+ },
+ rfi: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'RFI ID' },
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ sticky: true,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ sticky: true,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'number', type: 'integer' },
+ { name: 'status', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'label' },
+ { name: 'color' }
+ ] },
+ { name: 'locked', type: 'boolean',
+ control_type: 'checkbox', toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'locked',
+ label: 'Locked',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } },
+ { name: 'title' },
+ { name: 'question' },
+ { name: 'answer' },
+ { name: 'sent_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp',
+ hint: 'Date when the RFI was sent. See ' \
+ "Timestamps and " \
+ 'Timezones for accepted date formats' },
+ { name: 'due_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'assigned_to_uids',
+ hint: 'Array of unique identifiers of users who ' \
+ 'are RFI assignees.' },
+ { name: 'assigned_to', type: 'array', of: 'object',
+ properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'updated_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp',
+ hint: 'Date when the RFI was sent. See ' \
+ "Timestamps and " \
+ 'Timezones for accepted date formats' },
+ { name: 'updated_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'created_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp' },
+ { name: 'created_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'photos', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'attachments', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'snapshots', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] },
+ { name: 'comments', type: 'object', properties: [
+ { name: 'total_count', type: 'integer' },
+ { name: 'url' }
+ ] }
+ ]
+ end
+ },
+ rfi_status: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'RFI status ID' },
+ { name: 'label' },
+ { name: 'color' }
+ ]
+ end
+ },
+ user: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'User ID' },
+ { name: 'email' },
+ { name: 'first_name' },
+ { name: 'last_name' },
+ { name: 'language' },
+ { name: 'role', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' }
+ ] },
+ { name: 'removed' }
+ ]
+ end
+ },
+ sheet: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'Sheet ID' },
+ { name: 'name' },
+ { name: 'version_name' },
+ { name: 'description' },
+ { name: 'tags', hint: 'An array of strings representing the' \
+ ' tags added to this sheet.' },
+ { name: 'published_by', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' },
+ { name: 'email' }
+ ] },
+ { name: 'published_at', type: 'date_time',
+ render_input: 'parse_iso8601_timestamp',
+ parse_output: 'parse_iso8601_timestamp',
+ hint: 'UTC date and time in ISO-8601 format.' },
+ { name: 'deleted', type: 'boolean',
+ control_type: 'checkbox',
+ toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'deleted',
+ label: 'Deleted',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } },
+ { name: 'uploaded_file_name' }
+ ]
+ end
+ },
+ sheet_packet: {
+ fields: lambda do |_connection, _config_fields|
+ [
+ { name: 'uid', label: 'Sheet packet ID' },
+ { name: 'file_url' },
+ { name: 'resource', type: 'object', properties: [
+ { name: 'uid' },
+ { name: 'url' }
+ ] },
+ { name: 'status' }
+ ]
+ end
+ },
+ custom_action_input: {
+ fields: lambda do |_connection, config_fields|
+ input_schema = parse_json(config_fields.dig('input', 'schema') || '[]')
+
+ [
+ {
+ name: 'path',
+ optional: false,
+ hint: 'Base URI is https://io.plangrid.com' \
+ ' - path will be appended to this URI. ' \
+ 'Use absolute URI to override this base URI.'
+ },
+ (
+ 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: call('make_schema_builder_fields_sticky',
+ input_schema)
+ }
+ 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
+ }
+ },
+ actions: {
+ custom_action: {
+ description: "Custom action " \
+ "in Plangrid",
+ help: {
+ body: 'Build your own Plangrid action for any Plangrid ' \
+ 'REST endpoint.',
+ learn_more_url: 'https://developer.plangrid.com/docs/',
+ learn_more_text: 'The Plangrid 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 patch delete].map { |verb| [verb.upcase, verb] }
+ }],
+
+ input_fields: lambda do |object_definitions|
+ object_definitions['custom_action_input']
+ end,
+
+ execute: lambda do |_connection, input|
+ verb = input['verb']
+ error("#{verb} not supported") if %w[get post put delete].exclude?(verb)
+ data = input.dig('input', 'data').presence || {}
+ case verb
+ when 'get'
+ response =
+ get(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end.compact
+
+ if response.is_a?(Array)
+ array_name = parse_json(input['output'] || '[]').
+ dig(0, 'name') || 'array'
+ { array_name.to_s => response }
+ elsif response.is_a?(Hash)
+ response
+ else
+ error('API response is not a JSON')
+ end
+ when 'post'
+ post(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end.compact
+ when 'patch'
+ patch(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end.compact
+ when 'delete'
+ delete(input['path'], data).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end.compact
+ end
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['custom_action_output']
+ end
+ },
+ create_project: {
+ title: 'Create project',
+ description: 'Create project in'\
+ ' Plangrid',
+ help: {
+ body: 'Create project action uses the' \
+ " Create Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/create-project',
+ learn_more_text: 'Create Project'
+ },
+ input_fields: lambda do |object_definitions|
+ object_definitions['project'].
+ ignored('uid', 'updated_at', 'latitude', 'longitude',
+ 'organization_id').
+ required('name')
+ end,
+ execute: lambda do |_connection, input|
+ post('projects').payload(input).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['project']
+ end,
+ sample_output: lambda do |_connection, _input|
+ get('/projects')&.dig('data', 0) || {}
+ end
+ },
+ update_project: {
+ title: 'Update project',
+ description: 'Update project in'\
+ ' Plangrid',
+ help: {
+ body: 'Update project action uses the' \
+ " Update Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/update-project',
+ learn_more_text: 'Update a Project'
+ },
+ input_fields: lambda do |object_definitions|
+ object_definitions['project'].required('uid').
+ ignored('updated_at', 'latitude', 'longitude', 'organization_id')
+ end,
+ execute: lambda do |_connection, input|
+ patch("/projects/#{input.delete('uid')}").payload(input).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['project']
+ end,
+ sample_output: lambda do |_connection, _input|
+ get('/projects')&.dig('data', 0) || {}
+ end
+ },
+ get_project_details: {
+ title: 'Get project info. by ID',
+ description: 'Get project info.'\
+ ' by ID in Plangrid',
+ help: {
+ body: 'Get project info. by ID action uses the' \
+ " Retrieve a Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/retrieve-a-project',
+ learn_more_text: 'Retrieve a Project'
+ },
+ input_fields: lambda do |object_definitions|
+ object_definitions['project'].only('uid').required('uid')
+ end,
+ execute: lambda do |_connection, input|
+ get("/projects/#{input['uid']}")
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['project']
+ end,
+ sample_output: lambda do |_connection, _input|
+ get('/projects')&.dig('data', 0) || {}
+ end
+ },
+ upload_document: {
+ title: 'Upload document to a project',
+ description: 'Upload document to a project'\
+ ' in Plangrid',
+ help: {
+ body: 'Upload document to a project action uses the' \
+ " Upload Document to Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/upload-' \
+ 'attachment-to-project',
+ learn_more_text: 'Upload Document to Project API'
+ },
+ summarize_input: %w[file_content],
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'content_type',
+ hint: 'Content type of the document\'s file. e.g. for pdf' \
+ ' application/pdf', optional: false },
+ { name: 'file_content', optional: false },
+ { name: 'name', optional: false,
+ label: 'Document name',
+ hint: 'Name of the document.' },
+ { name: 'folder', label: 'Folder', sticky: true,
+ control_type: 'select',
+ pick_list: 'project_folders',
+ pick_list_params: { project_uid: 'project_uid' },
+ toggle_hint: 'Select folder',
+ hint: 'Folder shows in select options only if at least one file' \
+ ' exist in the folder',
+ toggle_field: {
+ name: 'folder',
+ label: 'Project folder',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use custom value',
+ hint: 'Folder in project to place the document ' \
+ '(case-sensitive). Leave blank to select root folder'
+ } },
+ { name: 'auto_version', type: 'boolean',
+ control_type: 'checkbox',
+ hint: 'Boolean indicating whether to automatically version the' \
+ ' document if a file with the same name exists within the ' \
+ 'specified folder. By default, documents are not versioned.',
+ toggle_hint: 'Select from options list',
+ toggle_field: {
+ name: 'auto_version',
+ type: 'boolean',
+ control_type: 'text',
+ label: 'Auto version',
+ toggle_hint: 'Use custom value',
+ hint: 'Allowed values are: true, false'
+ } }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ file_content = input.delete('file_content')
+ project_uid = input['project_uid']
+ file_upload_info = post("/projects/#{project_uid}/" \
+ 'attachments/uploads').
+ headers('Content-type': 'application/json').
+ payload(input)
+ url = file_upload_info&.dig('aws_post_form_arguments', 'action')
+ fields = file_upload_info&.dig('aws_post_form_arguments', 'fields')
+ # webhook_url = file_upload_info.
+ # dig('aws_post_form_arguments', 'webhook_url')
+ headers = fields.map { |o| { o['name'] => o['value'] } }.inject(:merge)
+ status =
+ post(url).
+ payload(key: headers['key'],
+ policy: headers['policy'],
+ signature: headers['signature'],
+ AWSAccessKeyId: headers['AWSAccessKeyId'],
+ 'content-type': headers['Content-Type'],
+ 'success_action_redirect': headers['success_action_redirect'],
+ 'x-amz-server-side-encryption':
+ headers['x-amz-server-side-encryption'],
+ 'x-amz-storage-class': headers['x-amz-storage-class'],
+ file: file_content).
+ request_format_multipart_form.
+ after_response do |_code, response, _response_headers|
+ response
+ end
+ status.merge('project_uid' => project_uid)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['document']
+ end,
+ sample_output: lambda do |_connection, _input|
+ {
+ uid: '147a420e-a182-4312-8fa5-4d10064d2f1a',
+ name: 'Bender',
+ folder: 'Specifications',
+ url: 'https://attachment-assets.plangrid.com/147a420e-a182-' \
+ '4312-8fa5-4d10064d2f1a.pdf',
+ created_at: '2013-05-17T02:30:22+00:00',
+ created_by: {
+ uid: null,
+ url: null,
+ email: 'nick@subcontractor.com'
+ },
+ deleted: false
+ }
+ end
+
+ },
+ create_rfi: {
+ title: 'Create RFI in a project',
+ description: 'Create RFI in'\
+ ' a Plangrid project ',
+ help: {
+ body: 'Create RFI in project action uses the ' \
+ "Create RFI in Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/create-rfi-' \
+ 'in-a-project',
+ learn_more_text: 'Create RFI in a Project'
+ },
+ input_fields: lambda do |object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'status', label: 'Status',
+ hint: 'Use this for create and update rfi status' }
+ ].concat(object_definitions['rfi'].
+ only('locked', 'title', 'question', 'answer', 'sent_at',
+ 'due_at', 'assigned_to_uids').
+ required('title'))
+ end,
+ execute: lambda do |_connection, input|
+ project_uid = input.delete('project_uid')
+ payload = input&.map do |key, val|
+ if %w[due_at sent_at].include?(key)
+ { key => val.to_time.utc.iso8601 }
+ else
+ { key => val }
+ end
+ end&.inject(:merge)
+ post("/projects/#{project_uid}/rfis").payload(payload).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end&.merge('project_uid' => project_uid)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['rfi']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/rfis")&.dig('data', 0) || {}
+ end
+ },
+ update_rfi: {
+ title: 'Update RFI in a project',
+ description: 'Update RFI in'\
+ ' a Plangrid project',
+ help: {
+ body: 'Update RFI in Project action uses the ' \
+ "patchUpdate RFI in a Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/update-' \
+ 'rfi-in-a-project',
+ learn_more_text: 'Update RFI in a Project'
+ },
+ input_fields: lambda do |object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'status', label: 'Status',
+ hint: 'Use this for create and update rfi status' }
+ ].concat(object_definitions['rfi'].
+ only('uid', 'locked', 'title', 'question', 'answer', 'sent_at',
+ 'due_at', 'assigned_to_uids').
+ required('uid'))
+ end,
+ execute: lambda do |_connection, input|
+ project_uid = input.delete('project_uid')
+ rfi_id = input.delete('uid')
+ payload = input&.map do |key, val|
+ if %w[due_at sent_at].include?(key)
+ { key => val.to_time.utc.iso8601 }
+ else
+ { key => val }
+ end
+ end&.inject(:merge)
+ patch("/projects/#{project_uid}/rfis/#{rfi_id}").payload(payload).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end&.merge('project_uid' => project_uid)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['rfi']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/rfis")&.dig('data', 0) || {}
+ end
+ },
+ get_rfi_in_project: {
+ title: 'Get RFI in a project',
+ description: 'Get RFI by ID in'\
+ ' Plangrid project',
+ help: {
+ body: 'Get RFI by ID in a project action uses the' \
+ " Retrieve RFI in a Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/" \
+ "retrieve-rfis-in-a-project',
+ learn_more_text: 'Retrieve RFI in a Project'
+ },
+ input_fields: lambda do |object_definitions|
+ object_definitions['rfi'].only('project_uid', 'uid').
+ required('project_uid', 'uid')
+ end,
+ execute: lambda do |_connection, input|
+ project_uid = input.delete('project_uid')
+ get("/projects/#{project_uid}/rfis/#{input['uid']}")&.
+ merge('project_uid' => project_uid)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['rfi']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/rfis")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ create_task: {
+ title: 'Create task in a project',
+ description: 'Create task in'\
+ ' Plangrid project',
+ help: {
+ body: 'Create task in Project action uses the ' \
+ "Create task in Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/create-task-in-' \
+ 'a-project',
+ learn_more_text: 'Create Task in a Project'
+ },
+ input_fields: lambda do |object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } }
+ ].concat(object_definitions['task'].
+ only('assigned_to_uids', 'cost_impact', 'description', 'due_at',
+ 'has_cost_impact', 'has_schedule_impact',
+ 'issue_list_uid', 'room', 'schedule_impact', 'start_date',
+ 'status', 'title', 'type'))
+ end,
+ execute: lambda do |_connection, input|
+ project_uid = input.delete('project_uid')
+ payload = input&.map do |key, val|
+ if %w[due_at].include?(key)
+ { key => val.to_time.utc.iso8601 }
+ else
+ { key => val }
+ end
+ end&.inject(:merge)
+ post("/projects/#{project_uid}/issues").payload(payload).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end&.merge('project_uid' => project_uid)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['task']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/issues")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ update_task: {
+ title: 'Update task in a Project',
+ description: 'Update task in'\
+ ' Plangrid project',
+ help: {
+ body: 'Update task in Project action uses the ' \
+ "Create task in Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/create-project',
+ learn_more_text: 'Update Task in a Project'
+ },
+ input_fields: lambda do |object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g.' \
+ ' 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } }
+ ].concat(object_definitions['task'].
+ only('uid', 'assigned_to_uids', 'cost_impact', 'description',
+ 'due_at', 'has_cost_impact', 'has_schedule_impact',
+ 'issue_list_uid', 'room', 'schedule_impact', 'start_date',
+ 'status', 'title', 'type').required('uid'))
+ end,
+ execute: lambda do |_connection, input|
+ project_uid = input.delete('project_uid')
+ patch("/projects/#{project_uid}/issues/" \
+ "#{input.delete('uid')}").payload(input).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end&.merge('project_uid' => project_uid)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['task']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/issues")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ get_task: {
+ title: 'Get task in a project',
+ description: 'Get task in'\
+ ' in a Plangrid project',
+ help: {
+ body: 'Get task in a project action uses the ' \
+ "Retrieve Task in a Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/retrieve-' \
+ 'issues-in-a-project',
+ learn_more_text: 'Retrieve Task in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'issue_uid', label: 'Issue ID', optional: false }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ project_uid = input.delete('project_uid')
+ get("/projects/#{project_uid}/issues/" \
+ "#{input['issue_uid']}").
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end&.merge('project_uid' => project_uid)
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['task']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/issues")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ invite_user_to_project: {
+ title: 'Invite a user to a project',
+ description: 'Invite user to'\
+ ' project team Plangrid',
+ help: {
+ body: 'Invite user to a project action uses the ' \
+ "Invite user in Project team API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'invite-user-to-project-team',
+ learn_more_text: 'Invite User to Project Team'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'email', optional: false },
+ { name: 'role_uid', label: 'Role ID',
+ hint: 'Unique identifier of role to assign user on project team' }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ post("/projects/#{input.delete('project_uid')}/users/invites").
+ payload(input).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['user']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/users")&.dig('data', 0) || {}
+ end
+ },
+ get_user_by_id: {
+ title: 'Get user in a project',
+ description: 'Get user in'\
+ ' Plangrid project',
+ help: {
+ body: 'Get user in project action uses the ' \
+ "Retrieve User on a Project Team.",
+ learn_more_url: 'https://developer.plangrid.com/docs/retrieve-' \
+ 'users-on-a-project-team',
+ learn_more_text: 'Retrieve User on a Project Team'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'user_uid', label: 'User ID', optional: false }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ get("/projects/#{input['project_uid']}/users/" \
+ "#{input['user_uid']}")
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['user']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/users")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ get_snapshot_in_project: {
+ title: 'Get snapshot in a project',
+ description: 'Get snapshot in'\
+ ' a Plangrid project',
+ help: {
+ body: 'Get snapshot in a project action uses the ' \
+ "Retrieve Snapshot in a Project.",
+ learn_more_url: 'https://developer.plangrid.com/docs/retrieve-' \
+ 'snapshot-in-a-project',
+ learn_more_text: 'Retrieve Snapshot in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'snapshot_uid', label: 'Snapshot ID', optional: false }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ get("/projects/#{input['project_uid']}/snapshots/" \
+ "#{input['snapshot_uid']}")
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['snapshot']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/snapshots")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ get_rfi_statuses_in_project: {
+ title: 'Get RFI statuses in project',
+ description: 'Get rfi statuses in'\
+ ' Plangrid project',
+ help: {
+ body: 'Get rfi statuses in project action uses the ' \
+ "Retrieve RFI Statuses in a Project.",
+ learn_more_url: 'https://developer.plangrid.com/docs/retrieve-' \
+ 'rfi-statuses-in-a-project',
+ learn_more_text: 'Retrieve RFI Statuses in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'limit', type: 'integer',
+ hint: 'Number of RFI statuses to retrieve. Maximum value of 50.' },
+ { name: 'skip', type: 'integer',
+ hint: 'Number of RFI statuses to skip in the set of results.' }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ { statuses:
+ get("/projects/#{input['project_uid']}/rfis/statuses")['data'] }
+ end,
+ output_fields: lambda do |object_definitions|
+ [
+ { name: 'statuses', type: 'array', of: 'object',
+ properties: object_definitions['rfi_status'] }
+ ]
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/rfis/statuses")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ get_roles_on_project: {
+ title: 'Get roles on a project',
+ description: 'Get roles on '\
+ ' Plangrid project',
+ help: {
+ body: 'Get role on a project action uses the ' \
+ "Retrieve Role on a Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-role-on-a-project',
+ learn_more_text: 'Retrieve Role on a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'limit', type: 'integer',
+ hint: 'Number of roles to retrieve. Maximum value of 50.' },
+ { name: 'skip', type: 'integer',
+ hint: 'Number of roles to skip in the set of results.' }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ { roles: get("/projects/#{input.delete('project_uid')}/roles", input) }
+ end,
+ output_fields: lambda do |_object_definitions|
+ [
+ { name: 'roles', type: 'array', of: 'object', properties: [
+ { name: 'uid' },
+ { name: 'label' }
+ ] },
+ { name: 'total_count' },
+ { name: 'next_page_url' }
+ ]
+ end,
+ sample_output: lambda do |_connection, _input|
+ { 'roles' => { 'uid' => '9d139e64-cac9-4f23-b4d5-9fd3688b498e',
+ 'label' => 'Admin' } }
+ end
+ },
+ get_sheets_in_project: {
+ title: 'Get sheets in a project',
+ description: 'Get sheets in'\
+ ' Plangrid project',
+ help: {
+ body: 'Get sheets in project action uses the ' \
+ "Retrieve Sheets in a Project.",
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-sheets-in-a-project',
+ learn_more_text: 'Retrieve Sheets in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'limit', type: 'integer',
+ hint: 'Number of sheets to retrieve. Maximum value of 50.' },
+ { name: 'skip', type: 'integer',
+ hint: 'Number of sheets to skip in the set of results' },
+ { name: 'updated_after', type: 'date_time',
+ hint: 'Only retrieve sheets created/updated after ' \
+ 'specified UTC date and time.' }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ { sheets: get("/projects/#{input.delete('project_uid')}/sheets",
+ input)['data'] }
+ end,
+ output_fields: lambda do |object_definitions|
+ [{ name: 'sheets', type: 'array', of: 'object',
+ properties: object_definitions['sheet'] }]
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ {
+ sheets: get("/projects/#{id}/sheets?limit=1")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ }
+ end
+ },
+ get_sheet_in_project: {
+ title: 'Get sheet by ID in project',
+ description: 'Get sheet in'\
+ ' Plangrid project',
+ help: {
+ body: 'Get project sheet details action uses the ' \
+ "Retrieve Sheets in a Project.",
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-a-sheet',
+ learn_more_text: 'Retrieve Sheet in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'sheet_uid', type: 'Sheet ID', optional: false }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ get("/projects/#{input['project_uid']}/sheets/#{input['sheet_uid']}")
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['sheet']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/sheets?limit=1")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ get_project_sheet_packet: {
+ title: 'Get project sheet packet',
+ description: 'Get project sheet packet in'\
+ ' packet in Plangrid',
+ help: {
+ body: 'Get project sheet packet action uses the ' \
+ "Retrieve Sheet Packet API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-sheet-packet',
+ learn_more_text: 'Retrieve Sheet Packet'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'packet_uid', type: 'Packet ID' }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ get("/projects/#{input['project_uid']}/sheets/packets/" \
+ "#{input['packet_uid']}")
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['sheet_packet']
+ end
+ },
+ get_field_reports_in_project: {
+ title: 'Get fields reports in project',
+ description: 'Get field reports in'\
+ ' in Plangrid project',
+ help: {
+ body: 'Get fields reports in project action uses the ' \
+ "Retrieve Field Reports in a" \
+ ' Project API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/retrieve-' \
+ 'field-reports-in-a-project',
+ learn_more_text: 'Retrieve Field Reports in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'updated_after', type: 'date_time',
+ hint: 'Only retrieve field reports created/updated after ' \
+ 'specified UTC date and time.' },
+ { name: 'report_date_min', type: 'date_time',
+ label: 'Report start date',
+ hint: 'Only retrieve field reports between a date range ' \
+ 'starting with this date in UTC format.' },
+ { name: 'report_date_max', type: 'date_time',
+ label: 'Report end date',
+ hint: 'Only retrieve field reports between a date range ' \
+ 'starting with this date in UTC format.' },
+ { name: 'sort_by', control_type: 'select',
+ pick_list:
+ %w[report_date updated_at]&.map { |e| [e.labelize, e] },
+ toggle_hint: 'Select sort by column',
+ toggle_field: {
+ name: 'sort_by', type: 'string', control_type: 'text',
+ hint: 'Allowed values report_date or updated_at'
+ } }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ project_uid = input.delete('project_uid')
+ query_params = ''
+ input&.map do |key, val|
+ if %w[updated_after report_date_min report_date_max].include?(key)
+ query_params = query_params + "#{key}=#{val.to_time.utc.iso8601}"
+ else
+ query_params = query_params + "#{key}=#{val}"
+ end
+ end
+ { field_reports: get("/projects/#{project_uid}/field_reports?" \
+ "#{query_params}")['data'] }
+ end,
+ output_fields: lambda do |object_definitions|
+ { name: 'field_reports', type: 'array', of: 'object',
+ properties: object_definitions['field_report'] }
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ { field_reports:
+ get("/projects/#{id}/rfis/field_reports")&.
+ merge('project_uid' => id) || {} }
+ end
+ },
+ upload_photo: {
+ title: 'Upload photo to project',
+ description: 'Upload photo to '\
+ ' project in Plangrid',
+ help: {
+ body: 'Upload photo to a project action uses the' \
+ " Upload Photo to Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/upload-photo' \
+ '-to-project',
+ learn_more_text: 'Upload Photo to Project'
+ },
+ summarize_input: %w[file_content],
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'content_type', optional: false,
+ hint: "Content type of the photo's file" },
+ { name: 'file_content', optional: false },
+ { name: 'title', optional: false, label: 'Photo title' }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ file_content = input.delete('file_content')
+ file_upload_info = post("/projects/#{input.delete('project_uid')}/" \
+ 'photos/uploads').
+ headers('Content-type': 'application/json').
+ payload(input)
+ url = file_upload_info&.dig('aws_post_form_arguments', 'action')
+ fields = file_upload_info&.dig('aws_post_form_arguments', 'fields')
+ # webhook_url = file_upload_info.
+ # dig('aws_post_form_arguments', 'webhook_url')
+ headers = fields.map { |o| { o['name'] => o['value'] } }.inject(:merge)
+ post(url).
+ payload(key: headers['key'],
+ policy: headers['policy'],
+ signature: headers['signature'],
+ AWSAccessKeyId: headers['AWSAccessKeyId'],
+ 'content-type': headers['Content-Type'],
+ 'success_action_redirect': headers['success_action_redirect'],
+ 'x-amz-server-side-encryption':
+ headers['x-amz-server-side-encryption'],
+ 'x-amz-storage-class': headers['x-amz-storage-class'],
+ file: file_content).
+ request_format_multipart_form.
+ after_response do |_code, response, _response_headers|
+ response
+ end
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['photo']
+ end,
+ sample_output: lambda do |_connection, _input|
+ {
+ uid: '147a420e-a182-4312-8fa5-4d10064d2f1a',
+ title: 'Bongo Drums',
+ url: 'https://attachment-assets.plangrid.com/147a420e-a182-' \
+ '4312-8fa5-4d10064d2f1a.pdf',
+ created_at: '2013-05-17T02:30:22+00:00',
+ created_by: {
+ uid: null,
+ url: null,
+ email: 'nick@subcontractor.com'
+ },
+ deleted: false
+ }
+ end
+ },
+ update_photo_metadata: {
+ title: 'Update photo metadata',
+ description: 'Update photo metadata to '\
+ ' Project in Plangrid',
+ help: {
+ body: 'Update photo metadata action uses the' \
+ " Update Document to Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/update-photo-' \
+ 'in-a-project',
+ learn_more_text: 'Update photo to Project API'
+ },
+ summarize_input: %w[file_content],
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'photo_uid', type: 'Photo ID', optional: false },
+ { name: 'title', label: 'Photo title',
+ hint: 'New title of the photo' }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ patch("/projects/#{input.delete('project_uid')}/photos/" \
+ "#{input.delete('photo_uid')}").
+ headers('Content-type': 'application/json').
+ payload(input).
+ after_error_response(/.*/) do |_code, body, _header, message|
+ error("#{message}: #{body}")
+ end
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['photo']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/photos")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ get_photo_by_id: {
+ title: 'Get photo in a Project',
+ description: 'Get photo in'\
+ ' a Plangrid project',
+ help: {
+ body: 'Get photo details action uses the ' \
+ "Retrieve Photo in Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-a-sheet',
+ learn_more_text: 'Retrieve Photo in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'photo_uid', type: 'Photo ID', optional: false }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ get("/projects/#{input['project_uid']}/photos/" \
+ "#{input['photo_uid']}")
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['photo']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/photos")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ },
+ get_document_by_id: {
+ title: 'Get document in a project',
+ description: 'Get document in'\
+ ' in Plangrid project',
+ help: {
+ body: 'Get document details action uses the ' \
+ "Retrieve Document in a Project API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-a-sheet',
+ learn_more_text: 'Retrieve Document in a Project API'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid',
+ control_type: 'select',
+ pick_list: 'project_list',
+ label: 'Project',
+ optional: false,
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ type: 'string',
+ control_type: 'text',
+ optional: false,
+ label: 'Project ID',
+ toggle_hint: 'Use project ID',
+ hint: 'Provide project ID e.g. ' \
+ '0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ { name: 'attachment_uid', type: 'Document ID', optional: false }
+ ]
+ end,
+ execute: lambda do |_connection, input|
+ get("/projects/#{input['project_uid']}/attachments/" \
+ "#{input['attachment_uid']}")
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['document']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/attachments")&.dig('data', 0)&.
+ merge('project_uid' => id) || {}
+ end
+ }
+ },
+ triggers: {
+ new_updated_project: {
+ title: 'New/updated project',
+ description: 'New/updated project in'\
+ ' Plangrid',
+ help: {
+ body: 'New/updated project trigger uses the' \
+ " List all Projects API.",
+ learn_more_url: 'https://developer.plangrid.com/docs/list-all-projects',
+ learn_more_text: 'List All Projects API'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 20
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get('/projects').
+ params(limit: limit,
+ skip: skip,
+ updated_after: updated_after)
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ {
+ events: response['data'] || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |project|
+ "#{project['uid']}@#{project['updated_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['project']
+ end,
+ sample_output: lambda do |_connection, _input|
+ get('/projects')&.dig('data', 0) || {}
+ end
+ },
+ new_updated_documents: {
+ title: 'New/updated documents in a project',
+ description: 'New/updated document in '\
+ 'Project Plangrid',
+ help: {
+ body: 'New/updated documents in Project trigger uses the' \
+ " Retrieve Documents in a Project" \
+ ' API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/retrieve-' \
+ 'attachments-in-a-project',
+ learn_more_text: 'Retrieve Documents in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ project_uid = closure&.[]('project_uid') || input['project_uid']
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 5
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get("/projects/#{project_uid}/attachments").
+ params(limit: limit,
+ skip: skip,
+ updated_after: updated_after)
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'project_uid' => project_uid,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'project_uid' => project_uid,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ documents = response['data']&.
+ map { |o| o.merge('project_uid' => project_uid) }
+ {
+ events: documents || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |document|
+ "#{document['uid']}@#{document['created_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['document']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/attachments")&.dig('data', 0) || {}
+ end
+ },
+ new_updated_task: {
+ title: 'New/updated task in a Project',
+ description: 'New/updated task in a '\
+ 'Project Plangrid',
+ help: {
+ body: 'New/updated task in a Project trigger uses the' \
+ " Retrieve Tasks " \
+ ' in a Project API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-issues-in-a-project',
+ learn_more_text: 'Retrieve Tasks in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ project_uid = closure&.[]('project_uid') || input['project_uid']
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 10
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get("/projects/#{input.delete('project_uid')}/" \
+ 'issues').
+ params(limit: limit,
+ skip: skip,
+ include_annotationless: true,
+ updated_after: updated_after)
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'project_uid' => project_uid,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'project_uid' => project_uid,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ tasks = response['data']&.
+ map { |o| o.merge('project_uid' => project_uid) }
+ {
+ events: tasks || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |task|
+ "#{task['uid']}@#{task['updated_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['task']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/issues")&.
+ dig('data', 0) || {}
+ end
+ },
+ new_updated_annotations: {
+ title: 'New/updated annotations in Project',
+ description: 'New/updated annotations '\
+ 'in Project Plangrid',
+ help: {
+ body: 'New/updated annotations in Project trigger uses the' \
+ " Retrieve " \
+ ' annotations in a Project API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-annotations-in-a-project',
+ learn_more_text: 'Retrieve Annotations in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ project_uid = closure&.[]('project_uid') || input['project_uid']
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 10
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get("/projects/#{input.delete('project_uid')}/" \
+ 'annotations').
+ params(limit: limit,
+ skip: skip,
+ updated_after: updated_after)
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'project_uid' => project_uid,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'project_uid' => project_uid,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ annotations = response['data']&.
+ map { |o| o.merge('project_uid' => project_uid) }
+ {
+ events: annotations || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |annotation|
+ "#{annotation['uid']}@#{annotation['updated_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['annotation']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/annotations")&.
+ dig('data', 0) || {}
+ end
+ },
+ new_updated_photos: {
+ title: 'New/updated photos in Project',
+ description: 'New/updated photos '\
+ 'in Plangrid Project',
+ help: {
+ body: 'New/updated photos in Project trigger uses the' \
+ " Retrieve " \
+ ' photos in a Project API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-photos-in-a-project',
+ learn_more_text: 'Retrieve photos in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ project_uid = closure&.[]('project_uid') || input['project_uid']
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 10
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get("/projects/#{input.delete('project_uid')}/" \
+ 'photos').
+ params(limit: limit,
+ skip: skip,
+ updated_after: updated_after)
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'project_uid' => project_uid,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'project_uid' => project_uid,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ photos = response['data']&.
+ map { |o| o.merge('project_uid' => project_uid) }
+ {
+ events: photos || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |photo|
+ "#{photo['uid']}@#{photo['updated_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['photo']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/photos")&.
+ dig('data', 0) || {}
+ end
+ },
+ new_updated_snapshot: {
+ title: 'New/updated snapshot in a Project',
+ description: 'New/updated snapshot '\
+ 'in a Project Plangrid',
+ help: {
+ body: 'New/updated snapshot in a Project trigger uses the' \
+ " Retrieve " \
+ ' Snapshots in a Project API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'remove-snapshot-reference-in-rfi',
+ learn_more_text: 'Retrieve Snapshots in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ project_uid = closure&.[]('project_uid') || input['project_uid']
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 10
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get("/projects/#{input.delete('project_uid')}/" \
+ 'snapshots').
+ params(limit: limit,
+ skip: skip,
+ updated_after: updated_after)
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'project_uid' => project_uid,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'project_uid' => project_uid,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ snapshots = response['data']&.
+ map { |o| o.merge('project_uid' => project_uid) }
+ {
+ events: snapshots || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |annotation|
+ "#{annotation['uid']}@#{annotation['updated_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['snapshot']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/snapshots")&.
+ dig('data', 0) || {}
+ end
+ },
+ new_updated_field_report: {
+ title: 'New/updated field report in a Project',
+ description: 'New/updated field report '\
+ 'in Plangrid Project',
+ help: {
+ body: 'New/updated field report in Project trigger uses the' \
+ " Retrieve " \
+ 'field reports in a Project API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-field-reports-in-a-project',
+ learn_more_text: 'Retrieve field reports in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ project_uid = closure&.[]('project_uid') || input['project_uid']
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 10
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get("/projects/#{input.delete('project_uid')}/" \
+ 'field_reports').
+ params(limit: limit,
+ skip: skip,
+ updated_after: updated_after,
+ sort_by: 'updated_at',
+ sort_order: 'asc')
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'project_uid' => project_uid,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'project_uid' => project_uid,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ field_reports = response['data']&.
+ map { |o| o.merge('project_uid' => project_uid) }
+ {
+ events: field_reports || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |field_report|
+ "#{field_report['uid']}@#{field_report['updated_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['field_report']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/field_reports")&.
+ dig('data', 0) || {}
+ end
+ },
+ new_updated_rfi: {
+ title: 'New/updated RFI in a Project',
+ description: 'New/updated RFI '\
+ 'in a Plangrid Project',
+ help: {
+ body: 'New/updated RFI in a Project trigger uses the' \
+ " Retrieve " \
+ ' RFIs in a Project API.',
+ learn_more_url: 'https://developer.plangrid.com/docs/' \
+ 'retrieve-rfis-in-a-project',
+ learn_more_text: 'Retrieve RFIs in a Project'
+ },
+ input_fields: lambda do |_object_definitions|
+ [
+ { name: 'project_uid', optional: false,
+ label: 'Project',
+ control_type: 'select', pick_list: 'project_list',
+ toggle_hint: 'Select project',
+ toggle_field: {
+ name: 'project_uid',
+ label: 'Project ID',
+ type: 'string',
+ control_type: 'text',
+ toggle_hint: 'Use project ID',
+ hint: 'Use Project ID e.g. 0bbb5bdb-3f87-4b46-9975-90e797ee9ff9'
+ } },
+ {
+ name: 'since',
+ label: 'When first started, this recipe should pick up events from',
+ hint: 'When you start recipe for the first time, ' \
+ 'it picks up trigger events from this specified date and time. ' \
+ 'Leave empty to get records created or updated one hour ago',
+ sticky: true,
+ type: 'timestamp'
+ }
+ ]
+ end,
+ poll: lambda do |_connection, input, closure|
+ project_uid = closure&.[]('project_uid') || input['project_uid']
+ updated_after = closure&.[]('updated_after') ||
+ (input['since'] || 1.hour.ago).to_time.utc.iso8601
+ limit = 10
+ skip = closure&.[]('skip') || 0
+ response = if (next_page_url = closure&.[]('next_page_url')).present?
+ get(next_page_url)
+ else
+ get("/projects/#{input.delete('project_uid')}/" \
+ 'rfis').
+ params(limit: limit,
+ skip: skip,
+ updated_after: updated_after)
+ end
+ closure = if (next_page_url = response['next_page_url']).present?
+ { 'skip' => skip + limit,
+ 'project_uid' => project_uid,
+ 'next_page_url' => next_page_url }
+ else
+ { 'offset' => 0,
+ 'project_uid' => project_uid,
+ 'updated_after' => now.to_time.utc.iso8601 }
+ end
+ rfis = response['data']&.
+ map { |o| o.merge('project_uid' => project_uid) }
+ {
+ events: rfis || [],
+ next_poll: closure,
+ can_poll_more: response['next_page_url'].present?
+ }
+ end,
+ dedup: lambda do |rfi|
+ "#{rfi['uid']}@#{rfi['updated_at']}"
+ end,
+ output_fields: lambda do |object_definitions|
+ object_definitions['rfi']
+ end,
+ sample_output: lambda do |_connection, _input|
+ id = get('projects')&.[]('data', 0)&.[]('uid')
+ get("/projects/#{id}/rfis")&.
+ dig('data', 0) || {}
+ end
+ }
+ },
+ pick_lists: {
+ project_list: lambda do |_connection|
+ get('projects')&.[]('data')&.pluck('name', 'uid')
+ end,
+ project_types: lambda do |_connection|
+ ['general', 'manufacturing', 'power', 'water-sewer-waste',
+ 'industrial-petroleum', 'transportation', 'hazardous-waste',
+ 'telecom', 'education-k-12', 'education-higher', 'gov-federal',
+ 'gov-state-local', 'other'].map { |type| [type.labelize, type] }
+ end,
+ project_folders: lambda do |_connection, project_uid:|
+ folders = get("/projects/#{project_uid}/attachments")['data']&.
+ pluck('folder')&.uniq
+ if folders.size > 0
+ folders&.map { |folder| [folder || 'Root', folder || ''] }
+ else
+ [['Root', '']]
+ end
+ end
+ }
+}