From 379e48bf1310427b0b20dea613ed181aff9c54a5 Mon Sep 17 00:00:00 2001 From: Goh ming xu bennett Date: Mon, 15 Jul 2019 18:10:18 +0800 Subject: [PATCH 1/4] First commit A branch for us to review the Allocadia custom connector built for OEM efforts --- custom_connectors/custom_auth/allocadia.rb | 1056 ++++++++++++++++++++ 1 file changed, 1056 insertions(+) create mode 100644 custom_connectors/custom_auth/allocadia.rb diff --git a/custom_connectors/custom_auth/allocadia.rb b/custom_connectors/custom_auth/allocadia.rb new file mode 100644 index 00000000..af9c53a9 --- /dev/null +++ b/custom_connectors/custom_auth/allocadia.rb @@ -0,0 +1,1056 @@ +{ + title: 'Allocadia', + + methods: { + 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, + + # convert a cells object from a line item to the readable format + format_item_cells: lambda do |input| + columns = input[:columns] + cells = input[:cells] + + new_cells = {} + + cells&.each do |id,cell| + col = columns[id] + + unless col == nil || cell['value'] == nil || cell['value'] == "" + new_cells[cell['columnName']] = call(:format_cell_value, { column: col, value: cell['value'] }) + # Need to use name rather than ID for YoY support, etc. + #new_cells["f_#{id}"] = call(:format_cell_value, { column: col, value: cell['value'] }) + end + end + + Hash[ new_cells.sort_by { |key, val| key } ] + end, + + # convert cell values from the Allocadia API into a readable format + format_cell_value: lambda do |input| + col = input[:column] + value = input[:value] + + case col['type'] + when 'DROPDOWN' + value = col['choices'].select {|choice| choice['id'] == value}[0]['label'] + + when 'MULTISELECT' + msStr = "" + value&.each do |id,pcnt| + choiceName = col['choices'].select {|choice| choice['id'] == id}[0]['label'] + msStr = msStr + choiceName + "::" + pcnt + "," + end + value = msStr[0...-1] # trims the trailing , from the end + + when 'LINK' + # if it's a link format value as either "label", "url", or "label (url)" depending on what's present + label = value['label'] + url = value['url'] + if (label && url) + value = label + " (" + url + ")" + elsif (label) + value = label + else + value = url + end + end + value + end, + + # convert an input cell into the format the Allocadia API expects + format_cell_to_allocadia: lambda do |input| + col = input[:column] + value = input[:value] + + value = (value == "") ? nil : value # treat empty string the same as nil + + if (value == nil && col['required']) + error("[#{col['name']}] is a required field. Cannot clear value.") + end + + unless value == nil + case col['type'] + when 'DROPDOWN' + value = col['choices'].select {|choice| choice['label'] == value}[0]['id'] + + when 'MULTISELECT' + msObj = {} + value&.split(",").each do |msVal| + choiceName = msVal.split("::")[0] + pcnt = msVal.split("::")[1] + choiceId = col['choices'].select {|choice| choice['label'] == choiceName}[0]['id'] + msObj[choiceId] = pcnt.to_f + end + value = msObj + + when 'LINK' + url = nil + label = value + # if the string ends in parentheses and the start inside parentheses is http then assume it's in label (url) format + if (value[-1] == ")") + urlStart = value&.rindex("(") + if (urlStart && value[urlStart+1..urlStart+4].casecmp("http") == 0) + url = value[urlStart+1...-1] + label = value[0...urlStart-1] + end + end + + # if it's not in label (url) format but appears to be a url then treat it as a url with no label + if (!url && label[0...4].casecmp("http") == 0) + url = label + label = nil + end + value = {} + value['label'] = label + value['url'] = url + end + end + value + end, + + # recursively check if the item is part of the hierarchy by following the parentId up the hierarchy + item_in_hierarchy: lambda do |input| + root_id = input[:root_id] + item = input[:item] + all_items = input[:all_items] + + if item['parentId'] == nil + item['id'] == root_id + elsif item['parentId'] == root_id + true + elsif + parent = all_items.select {|find_item| find_item['id'] == item['parentId']}[0] + call(:item_in_hierarchy, { root_id: root_id, all_items: all_items, item: parent }) + end + end, + + # retrieve all of the columns that are applicable to line items - "filter" input optional + get_line_item_columns_raw: lambda do |input| + get("/v1/budgets/#{input[:budgetId]}/columns?$filter=location ne \"ACTUAL\" and location ne \"PO\" and location ne \"ROLLUP\" and location ne \"BUDGET\" and location ne \"OTHER\"#{input[:filter]}") + end, + + # retrieve all of the columns that are applicable to line items, in a hash key'd by id + get_line_item_columns_key_id: lambda do |input| + cols = {} + call(:get_line_item_columns_raw, { budgetId: input[:budgetId], filter: input[:filter] } ).each do |col| + cols[col['id']] = col + end + cols + end, + + # retrieve all of the columns that are applicable to line items, in a hash key'd by name + get_line_item_columns_key_name: lambda do |input| + cols = {} + call(:get_line_item_columns_raw, { budgetId: input[:budgetId], filter: input[:filter] } ).each do |col| + cols[col['name']] = col + end + cols + end, + }, + + connection: { + fields: [ + { + name: 'username', + hint: 'Allocadia app login username', + optional: false + }, + { + name: 'password', + hint: 'Allocadia app login password', + optional: false, + control_type: 'password' + }, + { + name: 'environment', + default: 'api-staging', + control_type: 'select', + pick_list: [ + %w[North\ America api-na], + %w[Europe api-eu], + %w[Staging api-staging], + %w[Europe\ Staging api-eu-staging], + %w[Dev api-dev] + ], + optional: false + } + ], + + base_uri: lambda { |connection| + domain = (connection['environment'].include? 'dev') ? 'allocadia.technology' : 'allocadia.com' + "https://#{connection['environment']}.#{domain}" + }, + + authorization: { + type: 'custom_auth', + + acquire: lambda do |connection| + domain = (connection['environment'].include? 'dev') ? 'allocadia.technology' : 'allocadia.com' + post("https://#{connection['environment']}.#{domain}/v1/token", + username: connection['username'], + password: connection['password']).compact + end, + + refresh_on: [401], + + apply: lambda { |connection| + headers('Authorization' => "token #{connection['token']}") + } + } + }, + + test: ->(_connection) { get('/v1/budgets') }, # TODO can we access users with a viewer? or maybe just do a post to get a token? + + object_definitions: { + + simple_choice: { + fields: lambda do |_connection, _config_fields| + [ + { name: 'id' } + ] + end + }, + + full_choice: { + fields: lambda do |_connection, _config_fields| + [ + { name: 'id' }, + { name: 'label' }, + { name: 'createdDate' }, + { name: 'updatedDate' }, + { name: 'externalAssociations', + type: 'array', + of: 'object', + properties: + [ + { name: 'externalId' }, + { name: 'type' }, + { name: 'createdDate' }, + { name: 'updatedDate' } + ] + } + ] + end + }, + + choice: { + fields: lambda do |_connection, _config_fields| + [ + { + control_type: 'text', + label: 'Choice ID', + type: 'string', + name: 'choiceId', + optional: false + }, + { + control_type: 'text', + label: 'Label', + type: 'string', + name: 'label', + optional: false + }, + { + control_type: 'text', + label: 'External ID', + type: 'string', + name: 'externalId', + optional: true + } + ] + end + }, + + column: { + fields: lambda do |_connection, _config_fields| + [ + { + control_type: 'text', + label: 'Column ID', + type: 'string', + name: 'columnId', + optional: false + }, + { + control_type: 'text', + label: 'Name', + type: 'string', + name: 'name', + optional: false + }, + { + control_type: 'text', + label: 'Type', + type: 'string', + name: 'type', + optional: false + }, + { + control_type: 'select', + pick_list: 'column_locations', + label: 'Location', + type: 'string', + name: 'location' + }, + { + label: 'Choices', + type: 'array', + of: 'object', + name: 'choices', + properties: #TODO - can we reuse the choice object definition here? + [ + { + control_type: 'text', + label: 'Choice ID', + type: 'string', + name: 'choiceId' + }, + { + control_type: 'text', + label: 'Label', + type: 'string', + name: 'label' + }, + { + control_type: 'text', + label: 'External ID', + type: 'string', + name: 'externalId' + } + ] + } + ] + end + }, + + budget: { + fields: lambda do |_connection, _config_fields| + [ + { + sticky: true, + control_type: 'text', + label: 'Folder/Budget ID', + type: 'string', + name: 'budgetId', + optional: false + }, + { + control_type: 'text', + label: 'Folder/Budget Name', + type: 'string', + name: 'name', + optional: false + }, + { + control_type: 'text', + label: 'Parent ID', + type: 'string', + name: 'parentId' + }, + { + control_type: 'text', + label: 'Currency', + type: 'string', + name: 'currency' + }, + { + control_type: 'text', + label: 'Notes', + type: 'string', + name: 'notes' + }, + { + control_type: 'checkbox', + label: 'Folder', + toggle_hint: 'Select from option list', + toggle_field: { + label: 'Folder', + control_type: 'text', + toggle_hint: 'Use custom value', + type: 'boolean', + name: 'folder' + }, + type: 'boolean', + name: 'folder' + }, + { + control_type: 'date_time', + label: 'Created date', + render_input: 'date_time_conversion', + parse_output: 'date_time_conversion', + type: 'date_time', + name: 'createdDate' + }, + { + control_type: 'date_time', + label: 'Updated date', + render_input: 'date_time_conversion', + parse_output: 'date_time_conversion', + type: 'date_time', + name: 'updatedDate' + }, + ] + end + }, + + line_item: { + fields: lambda do |_connection, config_fields| + budget_id = config_fields['budgetId'] ? config_fields['budgetId'] : config_fields['updateBudgetId'] + read_only_filter = config_fields['updateBudgetId'] ? " and readOnly eq false" : "" # if this an update, only get non-readOnly columns + + cells_prop = + if (budget_id) && (budget_id&.to_i) != 0 + line_item_columns = call(:get_line_item_columns_raw, { budgetId: budget_id, filter: read_only_filter }) + line_item_columns&.map do |field| + case field['type'] + when 'CURRENCY', 'NUMBER' + { + name: "#{field['name']}", + #name: "f_#{field['id']}", + label: "#{field['name']}", + sticky: true, + control_type: 'number', + render_input: 'float_conversion', + parse_output: 'float_conversion', + type: 'number' + }.compact + else + { + name: "#{field['name']}", + #name: "f_#{field['id']}", + label: "#{field['name']}", + sticky: true, + control_type: 'text', + type: 'string' + }.compact + end + end + end || [] + [ + { + name: 'itemId', + label: 'Item ID', + control_type: 'text', + type: 'string' + }, + { + name: 'name', + label: 'Name', + control_type: 'text', + type: 'string' + }, + { + name: 'type', + label: 'Type', + default: 'LINE_ITEM', + control_type: 'select', + pick_list: 'line_item_types', + toggle_hint: 'Select from list', + toggle_field: { + name: 'type', + label: 'Type', + hint: 'Allowed values are: LINE_ITEM, CATEGORY, PLACEHOLDER', + toggle_hint: 'Use custom value', + control_type: 'text', + type: 'string' + } + }, + { + control_type: 'text', + label: 'Budget ID', + type: 'string', + name: 'budgetId' + }, + { + control_type: 'text', + label: 'Parent ID', + type: 'string', + name: 'parentId' + }, + { + control_type: 'text', + label: 'Path', + type: 'string', + name: 'path' + }, + { + name: 'createdDate', + label: 'Created date', + control_type: 'date_time', + render_input: 'date_time_conversion', + parse_output: 'date_time_conversion', + type: 'date_time' + }, + { + name: 'updatedDate', + label: 'Updated date', + control_type: 'date_time', + render_input: 'date_time_conversion', + parse_output: 'date_time_conversion', + type: 'date_time' + }, + { + name: 'cells', + sticky: true, + type: cells_prop.size > 0 ? 'object' : 'string', + properties: cells_prop + }, + + ] + end + }, + + filter: { + fields: lambda do |_connection, _config_fields| + [ + { + name: 'filter', + label: 'Filter using custom criteria', + sticky: true, + hint: 'Data can be filtered based upon property and ' \ + 'sub-property values. Strings must be double quoted and ' \ + 'all expressions must evaluate to a boolean value.
' \ + 'Supported operators: eq (Equal), ne (Not ' \ + 'equal), gt (Greater than), ge (Greater ' \ + 'than or equal), lt (Less than), le ' \ + '(Less than or equal), and, and or.
' \ + 'For example: updatedDate ge "2017-03-10T00:00:00.000Z" and ' \ + 'updatedDate lt "2017-03-11T00:00:00.000Z"' + } + ] + end + }, + + }, + + actions: { + + get_choices_by_column_id: { + description: "Get choices by column ID " \ + "in Allocadia", + + execute: lambda do |_connection, input| + + raw_choices = get("/v1/budgets/#{input['budgetId']}/columns/#{input['columnId']}/choices") + { + choices: raw_choices&.map do |field| + { + choiceId: "#{field['id']}", + label: "#{field['label']}", + externalId: "#{field['externalAssociations'].size > 0 ? field['externalAssociations'][0]['externalId'] : nil}" + } + end + } + end, + + input_fields: lambda do |object_definitions| + object_definitions['budget'].only('budgetId').concat(object_definitions['column'].only('columnId')).required('budgetId','columnId') + end, + + output_fields: lambda do |object_definitions| + [{ + name: 'choices', + type: 'array', + of: 'object', + properties: object_definitions['choice'] + }] + end, + + }, + + get_choices_by_column_name: { + description: "Get choices by column name " \ + "in Allocadia", + + execute: lambda do |_connection, input| + + allColumnsObj = [] + + locationFilter = input['location'] ? "location eq \"#{input['location']}\" and " : "" + + column_names = input['columnNames']&.map { |item| item['columnName'] } || [] + column_names.each do |columnName| + columnObj = get("/v1/budgets/#{input['budgetId']}/columns?$filter=#{locationFilter}name eq \"#{columnName}\"").first || error("Column #{columnName} not found") + + raw_choices = get("/v1/budgets/#{input['budgetId']}/columns/#{columnObj['id']}/choices") + choices = raw_choices&.map do |choice| + { + choiceId: "#{choice['id']}", + label: "#{choice['label']}", + externalId: "#{choice['externalAssociations'].size > 0 ? choice['externalAssociations'][0]['externalId'] : nil}" + } + end + + columnObj['choices'] = choices + allColumnsObj << columnObj + end + + { + columns: allColumnsObj + } + end, + + input_fields: lambda do |object_definitions| + object_definitions['budget'].only('budgetId'). + concat([ + { + name: 'columnNames', + type: :array, + of: :object, + properties: [{ name: 'columnName' }] + } + ]).concat(object_definitions['column'].only('location')) + end, + + output_fields: lambda do |object_definitions| + [{ + name: 'columns', + type: 'array', + of: 'object', + properties: object_definitions['column'] + }] + end, + + }, + + add_choice: { + description: "Add column choice " \ + "in Allocadia", + + execute: lambda do |_connection, input| + + param = '{ "label":"' + input['label'] + '", "externalAssociations": [{ "externalId": "' + input['externalId'] + '", "type": "CAMPAIGN" } ] }' + + post("/v1/budgets/#{input['budgetId']}/columns/#{input['columnId']}/choices", parse_json(param)) + .after_response do |code, body, headers| + # if /3\d{2} | 4\d{2} | 5\d{2}/.match?(code) + if code.to_s.match?(/[3-5]\d{2}/) + error("#{code}: #{body}") + else + { choiceId: headers['location'].split('/').last } + end + end + + end, + + input_fields: lambda do |object_definitions| + object_definitions['budget'].only('budgetId').concat(object_definitions['column'].only('columnId')).concat(object_definitions['choice'].ignored('choiceId')) + end, + + output_fields: lambda do |object_definitions| + [{ + name: 'choiceId', + }] + end, + + }, + + get_item_by_id: { + description: "Get item by ID " \ + "in Allocadia", + + execute: lambda do |_connection, input| + + item = get("/v1/lineitems/#{input['itemId']}") + columns = call(:get_line_item_columns_raw, { budgetId: item['budgetId'] }) + + if input['primaryExternalColumnName'] + primaryColId = columns.select {|col| col['name'] == input['primaryExternalColumnName'] }[0]['id'] + choices = get("/v1/budgets/#{item['budgetId']}/columns/#{primaryColId}/choices") + if (item['cells'][primaryColId]) + choiceId = item['cells'][primaryColId]['value'] + choice = choices.select {|ch| ch['id'] == choiceId }[0] + externalId = choice['externalAssociations'].size > 0 ? choice['externalAssociations'][0]['externalId'] : nil + item['primaryExternalId'] = externalId + end + end + + cols = {} + columns.each do |col| + cols[col['id']] = col + end + item['cells'] = call(:format_item_cells, { columns: cols, cells: item['cells'] }) + + item.except('_links') + end, + + config_fields: [{ + name: 'budgetId', + label: 'Budget', + optional: false, + control_type: 'select', + type: 'text', + pick_list: 'budgets', + toggle_hint: 'Select from list', + toggle_field: { + name: 'budgetId', + label: 'Budget ID', + toggle_hint: 'Use custom value', + hint: 'Enter N/A if budget ID not known', + control_type: 'text', + type: 'string' + } + }], + + input_fields: lambda do |object_definitions| + object_definitions['line_item'].only('itemId').required('itemId').concat([{ name: 'primaryExternalColumnName' }]) + + end, + + output_fields: lambda do |object_definitions| + object_definitions['line_item'].concat([{ name: 'primaryExternalId' }]) + end, + + }, + + get_budget_by_id: { + description: "Get budget by ID " \ + "in Allocadia", + + execute: lambda do |_connection, input| + budget = get("/v1/budgets/#{input['budgetId']}") + budget['budgetId'] = budget.delete('id') + budget.except('_links') + end, + + input_fields: lambda do |object_definitions| + object_definitions['budget'].only('budgetId') + + end, + + output_fields: lambda do |object_definitions| + object_definitions['budget'] + end, + + }, + + get_all_budgets: { + description: "Get all budgets by folder ID " \ + "in Allocadia", + + execute: lambda do |_connection, input| + budget_filter = input['filter'] ? "?$filter=#{input['filter']}" : "" + all_budgets = get("/v1/budgets#{budget_filter}") + budgets_in_hierarchy = input['folderId'].blank? ? all_budgets : all_budgets.select {|item| call(:item_in_hierarchy, { root_id: input['folderId'], all_items: all_budgets, item: item }) } + + budgets_in_hierarchy.each do |budget| + budget['budgetId'] = budget.delete 'id' + budget.delete '_links' + end + + { budgets: budgets_in_hierarchy } + end, + + input_fields: lambda do |object_definitions| + [{ + name: 'folderId', + label: 'Folder ID', + }].concat(object_definitions['filter']) + end, + + + output_fields: lambda do |object_definitions| + [{ + name: 'budgets', + type: 'array', + of: 'object', + properties: object_definitions['budget'] + }] + end, + + }, + + get_line_items_by_budget: { + description: "Get all line items in budget " \ + "in Allocadia", + + execute: lambda do |_connection, input| + filter = input['filter'] ? "?$filter=#{input['filter']}" : "" + columns = call(:get_line_item_columns_key_id, { budgetId: input['budgetId'] }) + budget_items = get("/v1/budgets/#{input['budgetId']}/lineitems#{filter}") + + budget_items&.delete_if { |item| item['parentId'] == nil } # filter out grand total row + + budget_items.each do |item| + item['itemId'] = item.delete 'id' + item.delete '_links' + item['cells'] = call(:format_item_cells, { columns: columns, cells: item['cells'] }) + end + + { items: budget_items } + end, + + # reduces output to the first 100 items plus the last one? + summarize_output: 'items', + + config_fields: [{ + name: 'budgetId', + label: 'Budget', + optional: false, + control_type: 'select', + type: 'text', + pick_list: 'budgets', + toggle_hint: 'Select from list', + toggle_field: { + name: 'budgetId', + label: 'Budget ID', + toggle_hint: 'Use custom value', + control_type: 'text', + type: 'string' + } + }], + + input_fields: lambda do |object_definitions| + object_definitions['filter'] + end, + + output_fields: lambda do |object_definitions| + [{ + name: 'items', + type: 'array', + of: 'object', + properties: object_definitions['line_item'] + }] + end, + }, + + search_line_items: { + description: "Search line items in folder " \ + "in Allocadia", + + execute: lambda do |_connection, input| + item_filter = input['filter'] ? "?$filter=#{input['filter']}" : "" + all_budgets = get("/v1/budgets") + budgets_in_hierarchy = all_budgets.select {|budget| call(:item_in_hierarchy, { root_id: input['folderId'], all_items: all_budgets, item: budget }) } + budgets_in_hierarchy = budgets_in_hierarchy.select {|budget| budget['folder'] == false } # include budgets, not folders + columns = call(:get_line_item_columns_key_id, { budgetId: input['folderId'] }) + + all_items = [] + + budgets_in_hierarchy.each do |budget| + budget_items = get("/v1/budgets/#{budget['id']}/lineitems#{item_filter}") + + budget_items.each do |item| + unless item['parentId'] == nil # filter out grand total row + item['itemId'] = item.delete 'id' + item.delete '_links' + item['cells'] = call(:format_item_cells, { columns: columns, cells: item['cells'] }) + all_items << item + end + end + end + + { items: all_items } + end, + + config_fields: [{ + name: 'folderId', + label: 'Folder', + optional: false, + control_type: 'select', + type: 'text', + pick_list: 'folders', + toggle_hint: 'Select from folder list', + toggle_field: { + name: 'folderId', + label: 'Folder ID', + toggle_hint: 'Use custom value', + control_type: 'text', + type: 'string' + } + }], + + input_fields: lambda do |object_definitions| + object_definitions['filter'] + end, + + output_fields: lambda do |object_definitions| + [{ + name: 'items', + type: 'array', + of: 'object', + properties: object_definitions['line_item'] + }] + end, + }, + + # TODO - this is incomplete - waiting on a "wait" function to be available + search_line_items_large_hierarchy: { + description: "Search line items in folder for large hierarchy " \ + "in Allocadia", + + execute: lambda do |_connection, input| + itemfilter = input['filter'] ? "?$filter=#{input['filter']}" : "" + #columns = call(:get_line_item_columns_key_id, { budgetId: input['budgetId'] }) + + budget_items = get("/v1/lineitems/#{itemfilter}").after_response do |code, body, headers| + location = headers['location'] + job_id = location&.split('/')[-1] + job = get("/v1/jobs/lineitems/#{job_id}") + error(job) + end + + all_items = [] + + budget_items.each do |item| + unless item['parentId'] == nil # filter out grand total row + item['itemId'] = item.delete 'id' + item.delete '_links' + # item['cells'] = call(:format_item_cells, { columns: columns, cells: item['cells'] }) + all_items << item + end + end + + { items: all_items } + end, + + input_fields: lambda do |object_definitions| + object_definitions['filter'] + end, + + output_fields: lambda do |object_definitions| + [{ + name: 'items', + type: 'array', + of: 'object', + properties: object_definitions['line_item'] + }] + end, + }, + + update_line_item: { + description: "Update line item" \ + " in Allocadia", + + execute: lambda do |_connection, input| + columns = call(:get_line_item_columns_key_name, { budgetId: input.delete('updateBudgetId') }) + + # handle case where it could be a cells object coming in as a string + if (input['cells']&.is_a?(String)) + input['cells'] = parse_json(input['cells']) + end + + input['cells'] = input['cells']&. + compact&. + map { |key, value| { columns[key]['id'] => { 'value' => call(:format_cell_to_allocadia, { value: value, column: columns[key] } ) } } }&. + inject(:merge) + + put("/v1/lineitems" \ + "/#{input.delete('itemId')}", input.compact) + .after_error_response(/.*/) do |_code, body, _header, message| + error("#{message}: #{body}") + end || {} + end, + + config_fields: [{ + name: 'updateBudgetId', + label: 'Budget', + optional: false, + control_type: 'select', + type: 'text', + pick_list: 'budgets', + toggle_hint: 'Select from list', + toggle_field: { + name: 'updateBudgetId', + label: 'Budget ID', + toggle_hint: 'Use custom value', + control_type: 'text', + type: 'string' + } + }], + + input_fields: lambda do |object_definitions| + object_definitions['line_item'].only('itemId','name','cells').required('itemId') + end + }, + + add_line_item: { + description: "Add line item" \ + " in Allocadia", + + execute: lambda do |_connection, input| + columns = call(:get_line_item_columns_key_name, { budgetId: input['updateBudgetId'] }) + + # handle case where it could be a cells object coming in as a string + if (input['cells']&.is_a?(String)) + input['cells'] = parse_json(input['cells']) + end + + input['cells'] = input['cells']&. + compact&. + map { |key, value| { columns[key]['id'] => { 'value' => call(:format_cell_to_allocadia, { value: value, column: columns[key] } ) } } }&. + inject(:merge) + + post("/v1/budgets/#{input.delete('updateBudgetId')}/lineitems", input.compact) + .after_response do |code, body, headers| + if code.to_s.match?(/[3-5]\d{2}/) + error("#{code}: #{body}") + else + { itemId: headers['location'].split('/').last } + end + end + end, + + config_fields: [{ + name: 'updateBudgetId', + label: 'Budget', + optional: false, + control_type: 'select', + type: 'text', + pick_list: 'budgets', + toggle_hint: 'Select from list', + toggle_field: { + name: 'updateBudgetId', + label: 'Budget ID', + toggle_hint: 'Use custom value', + control_type: 'text', + type: 'string' + } + }], + + input_fields: lambda do |object_definitions| + object_definitions['line_item'].only('name','type','parentId','cells').required('name','type') + end, + + output_fields: lambda do |object_definitions| + object_definitions['line_item'].only('itemId') + end, + }, + + }, + + pick_lists: { + foldersbudgets: ->(_connection) { get('/v1/budgets')&.pluck('name', 'id') || [] }, + budgets: ->(_connection) { get('/v1/budgets?$filter=folder eq false')&.pluck('name', 'id') || [] }, + folders: ->(_connection) { get('/v1/budgets?$filter=folder eq true')&.pluck('name', 'id') || [] }, + + line_item_types: lambda do |_connection| + [%w[Line\ item LINE_ITEM], + %w[Category CATEGORY], + %w[Placeholder PLACEHOLDER]] + end, + + column_locations: lambda do |_connection| + [ + ["ACTUAL","ACTUAL"], + ["BUDGET","BUDGET"], + ["DETAILS","DETAILS"], + ["GRID","GRID"], + ["OTHER","OTHER"], + ["PO","PO"], + ["ROLLUP","ROLLUP"] + ] + end + } +} From d348bee089483ed78ac455573976b6a186f2143b Mon Sep 17 00:00:00 2001 From: Bennett Goh Date: Fri, 2 Aug 2019 09:47:49 +0800 Subject: [PATCH 2/4] Review changes --- custom_connectors/custom_auth/allocadia.rb | 470 ++++++++++++++------- 1 file changed, 316 insertions(+), 154 deletions(-) diff --git a/custom_connectors/custom_auth/allocadia.rb b/custom_connectors/custom_auth/allocadia.rb index af9c53a9..c952fb10 100644 --- a/custom_connectors/custom_auth/allocadia.rb +++ b/custom_connectors/custom_auth/allocadia.rb @@ -22,17 +22,17 @@ cells = input[:cells] new_cells = {} - + cells&.each do |id,cell| col = columns[id] - - unless col == nil || cell['value'] == nil || cell['value'] == "" + + unless col.blank? || cell['value'].blank? new_cells[cell['columnName']] = call(:format_cell_value, { column: col, value: cell['value'] }) # Need to use name rather than ID for YoY support, etc. #new_cells["f_#{id}"] = call(:format_cell_value, { column: col, value: cell['value'] }) end end - + Hash[ new_cells.sort_by { |key, val| key } ] end, @@ -40,24 +40,24 @@ format_cell_value: lambda do |input| col = input[:column] value = input[:value] - + case col['type'] when 'DROPDOWN' - value = col['choices'].select {|choice| choice['id'] == value}[0]['label'] - + value = col['choices'].select {|choice| choice['id'] == value}.dig(0, 'label') + when 'MULTISELECT' - msStr = "" + ms_array = [] value&.each do |id,pcnt| - choiceName = col['choices'].select {|choice| choice['id'] == id}[0]['label'] - msStr = msStr + choiceName + "::" + pcnt + "," + choice_name = col['choices'].select {|choice| choice['id'] == id}.dig(0, 'label') + ms_array << choice_name + "::" + pcnt end - value = msStr[0...-1] # trims the trailing , from the end - + value = ms_array.join(',') + when 'LINK' # if it's a link format value as either "label", "url", or "label (url)" depending on what's present label = value['label'] url = value['url'] - if (label && url) + if (label && url) value = label + " (" + url + ")" elsif (label) value = label @@ -72,53 +72,48 @@ format_cell_to_allocadia: lambda do |input| col = input[:column] value = input[:value] - - value = (value == "") ? nil : value # treat empty string the same as nil - - if (value == nil && col['required']) + + if (value.blank? && col['required']) error("[#{col['name']}] is a required field. Cannot clear value.") end - - unless value == nil - case col['type'] - when 'DROPDOWN' - value = col['choices'].select {|choice| choice['label'] == value}[0]['id'] - - when 'MULTISELECT' - msObj = {} - value&.split(",").each do |msVal| - choiceName = msVal.split("::")[0] - pcnt = msVal.split("::")[1] - choiceId = col['choices'].select {|choice| choice['label'] == choiceName}[0]['id'] - msObj[choiceId] = pcnt.to_f - end - value = msObj - - when 'LINK' - url = nil - label = value - # if the string ends in parentheses and the start inside parentheses is http then assume it's in label (url) format - if (value[-1] == ")") - urlStart = value&.rindex("(") - if (urlStart && value[urlStart+1..urlStart+4].casecmp("http") == 0) - url = value[urlStart+1...-1] - label = value[0...urlStart-1] + + if value.present? + case col['type'] + when 'DROPDOWN' + value = col['choices'].select {|choice| choice['label'] == value}.dig(0, 'id') + + when 'MULTISELECT' + ms_obj = {} + value&.split(",").each do |ms_val| + choice_name_pcnt_arr = ms_val.split("::") + choice_id = col['choices'].select {|choice| choice['label'] == choice_name_pcnt_arr[0]}.dig(0, 'id') + ms_obj[choice_id] = choice_name_pcnt_arr[1].to_f end + value = ms_obj + + when 'LINK' + url = nil + label = value + # if the string ends in parentheses and the start inside parentheses is http then assume it's in label (url) format + label_url = value&.match(/(.*) (\()(http.*)(\))/i) # /i will ignore case + if (label_url.present?) + label = label_url[1] + url = label_url[3] + end + + # if it's not in label (url) format but appears to be a url then treat it as a url with no label + if (!url && label&.match(/^http.*/i)) + url = label + label = nil + end + value = {} + value['label'] = label + value['url'] = url end - - # if it's not in label (url) format but appears to be a url then treat it as a url with no label - if (!url && label[0...4].casecmp("http") == 0) - url = label - label = nil - end - value = {} - value['label'] = label - value['url'] = url end - end - value + value.blank? ? nil : value # treat empty string the same as nil end, - + # recursively check if the item is part of the hierarchy by following the parentId up the hierarchy item_in_hierarchy: lambda do |input| root_id = input[:root_id] @@ -129,17 +124,17 @@ item['id'] == root_id elsif item['parentId'] == root_id true - elsif + else parent = all_items.select {|find_item| find_item['id'] == item['parentId']}[0] call(:item_in_hierarchy, { root_id: root_id, all_items: all_items, item: parent }) end end, - # retrieve all of the columns that are applicable to line items - "filter" input optional + # retrieve all of the columns that are applicable to line items - "filter" input optional get_line_item_columns_raw: lambda do |input| get("/v1/budgets/#{input[:budgetId]}/columns?$filter=location ne \"ACTUAL\" and location ne \"PO\" and location ne \"ROLLUP\" and location ne \"BUDGET\" and location ne \"OTHER\"#{input[:filter]}") end, - + # retrieve all of the columns that are applicable to line items, in a hash key'd by id get_line_item_columns_key_id: lambda do |input| cols = {} @@ -148,7 +143,7 @@ end cols end, - + # retrieve all of the columns that are applicable to line items, in a hash key'd by name get_line_item_columns_key_name: lambda do |input| cols = {} @@ -205,39 +200,31 @@ refresh_on: [401], apply: lambda { |connection| - headers('Authorization' => "token #{connection['token']}") + # if token doesn't exist yet use a dummy value so that we get a 401 and not a 400 error + headers('Authorization' => "token #{connection['token'] ? connection['token'] : '1234'}") } } }, - test: ->(_connection) { get('/v1/budgets') }, # TODO can we access users with a viewer? or maybe just do a post to get a token? + test: ->(_connection) { post("/v1/token", + username: _connection['username'], + password: _connection['password']) }, + + # todo - look to add hints where applicable object_definitions: { - simple_choice: { + choice_add: { fields: lambda do |_connection, _config_fields| [ - { name: 'id' } - ] - end - }, - - full_choice: { - fields: lambda do |_connection, _config_fields| - [ - { name: 'id' }, - { name: 'label' }, - { name: 'createdDate' }, - { name: 'updatedDate' }, + { name: 'label', optional: false }, { name: 'externalAssociations', type: 'array', of: 'object', - properties: + properties: [ - { name: 'externalId' }, - { name: 'type' }, - { name: 'createdDate' }, - { name: 'updatedDate' } + { name: 'externalId', sticky: true }, + { name: 'type', default: 'CAMPAIGN' }, ] } ] @@ -300,6 +287,14 @@ control_type: 'select', pick_list: 'column_locations', label: 'Location', + toggle_hint: 'Select from option list', + toggle_field: { + label: 'Location', + control_type: 'text', + toggle_hint: 'Use custom value', + type: 'boolean', + name: 'location' + }, type: 'string', name: 'location' }, @@ -334,7 +329,7 @@ end }, - budget: { + budget: { fields: lambda do |_connection, _config_fields| [ { @@ -399,7 +394,7 @@ parse_output: 'date_time_conversion', type: 'date_time', name: 'updatedDate' - }, + } ] end }, @@ -407,29 +402,28 @@ line_item: { fields: lambda do |_connection, config_fields| budget_id = config_fields['budgetId'] ? config_fields['budgetId'] : config_fields['updateBudgetId'] - read_only_filter = config_fields['updateBudgetId'] ? " and readOnly eq false" : "" # if this an update, only get non-readOnly columns - + # if this an update, only get non-readOnly columns + read_only_filter = config_fields['updateBudgetId'] ? ' and readOnly eq false' : '' + cells_prop = - if (budget_id) && (budget_id&.to_i) != 0 - line_item_columns = call(:get_line_item_columns_raw, { budgetId: budget_id, filter: read_only_filter }) + if budget_id && (budget_id&.to_i) != 0 + line_item_columns = call(:get_line_item_columns_raw, budgetId: budget_id, filter: read_only_filter) line_item_columns&.map do |field| - case field['type'] - when 'CURRENCY', 'NUMBER' + case field['type'] + when 'CURRENCY', 'NUMBER' { - name: "#{field['name']}", - #name: "f_#{field['id']}", - label: "#{field['name']}", + name: field['name'], + label: field['name'], sticky: true, control_type: 'number', render_input: 'float_conversion', parse_output: 'float_conversion', type: 'number' }.compact - else + else { - name: "#{field['name']}", - #name: "f_#{field['id']}", - label: "#{field['name']}", + name: field['name'], + label: field['name'], sticky: true, control_type: 'text', type: 'string' @@ -510,7 +504,7 @@ ] end }, - + filter: { fields: lambda do |_connection, _config_fields| [ @@ -532,23 +526,187 @@ 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://#{connection['environment']}" \ + '.allocadia.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: 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 + }, + }, actions: { + custom_action: { + description: "Custom http action " \ + "in Allocadia", + help: { + body: 'Build your own Allocadia action with an HTTP request. The ' \ + 'request will be authorized with your Allocadia connection.', + learn_more_url: "https://api-na.allocadia.com/v1/docs/", + learn_more_text: 'Allocadia API Documentation' + }, + + 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_response do |code, body, headers| + # if /3\d{2} | 4\d{2} | 5\d{2}/.match?(code) + if code.to_s.match?(/[3-5]\d{2}/) + error("#{code}: #{body}") + else + { + location: headers['location'], + id: headers['location'].split('/').last + } + end + # .after_error_response(/.*/) do |_code, body, _header, message| + # error("#{message}: #{body}") + end.compact + when 'put' + put(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, + + 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 { |verb| [verb.upcase, verb] } + }], + + input_fields: lambda do |object_definitions| + object_definitions['custom_action_input'] + end, + + output_fields: lambda do |object_definitions| + object_definitions['custom_action_output'] + end + }, + get_choices_by_column_id: { - description: "Get choices by column ID " \ + description: "Get choices by column ID " \ "in Allocadia", execute: lambda do |_connection, input| - + raw_choices = get("/v1/budgets/#{input['budgetId']}/columns/#{input['columnId']}/choices") - { + { choices: raw_choices&.map do |field| { - choiceId: "#{field['id']}", - label: "#{field['label']}", - externalId: "#{field['externalAssociations'].size > 0 ? field['externalAssociations'][0]['externalId'] : nil}" + choiceId: field['id'], + label: field['label'], + externalId: field.dig('externalAssociations', 0, 'externalId') } end } @@ -570,34 +728,35 @@ }, get_choices_by_column_name: { - description: "Get choices by column name " \ + description: "Get choices by column name " \ "in Allocadia", execute: lambda do |_connection, input| - - allColumnsObj = [] - + + all_columns_obj = [] + locationFilter = input['location'] ? "location eq \"#{input['location']}\" and " : "" - - column_names = input['columnNames']&.map { |item| item['columnName'] } || [] + + column_names = input['columnNames'].pluck('columnName') + column_names.each do |columnName| - columnObj = get("/v1/budgets/#{input['budgetId']}/columns?$filter=#{locationFilter}name eq \"#{columnName}\"").first || error("Column #{columnName} not found") - - raw_choices = get("/v1/budgets/#{input['budgetId']}/columns/#{columnObj['id']}/choices") + column_obj = get("/v1/budgets/#{input['budgetId']}/columns?$filter=#{locationFilter}name eq \"#{columnName}\"").first || error("Column #{columnName} not found") + + raw_choices = get("/v1/budgets/#{input['budgetId']}/columns/#{column_obj['id']}/choices") choices = raw_choices&.map do |choice| { - choiceId: "#{choice['id']}", - label: "#{choice['label']}", - externalId: "#{choice['externalAssociations'].size > 0 ? choice['externalAssociations'][0]['externalId'] : nil}" + choiceId: choice['id'], + label: choice['label'], + externalId: choice.dig('externalAssociations', 0, 'externalId') } end - - columnObj['choices'] = choices - allColumnsObj << columnObj - end - + + column_obj['choices'] = choices + all_columns_obj << column_obj + end + { - columns: allColumnsObj + columns: all_columns_obj } end, @@ -623,16 +782,19 @@ end, }, - + add_choice: { description: "Add column choice " \ "in Allocadia", execute: lambda do |_connection, input| - param = '{ "label":"' + input['label'] + '", "externalAssociations": [{ "externalId": "' + input['externalId'] + '", "type": "CAMPAIGN" } ] }' + # if the externalId in the first entry is blank, remove the external associations + if input.dig('externalAssociations',0,'externalId').blank? + input.delete('externalAssociations') + end - post("/v1/budgets/#{input['budgetId']}/columns/#{input['columnId']}/choices", parse_json(param)) + post("/v1/budgets/#{input.delete('budgetId')}/columns/#{input.delete('columnId')}/choices", input) .after_response do |code, body, headers| # if /3\d{2} | 4\d{2} | 5\d{2}/.match?(code) if code.to_s.match?(/[3-5]\d{2}/) @@ -645,7 +807,7 @@ end, input_fields: lambda do |object_definitions| - object_definitions['budget'].only('budgetId').concat(object_definitions['column'].only('columnId')).concat(object_definitions['choice'].ignored('choiceId')) + object_definitions['budget'].only('budgetId').concat(object_definitions['column'].only('columnId')).concat(object_definitions['choice_add']) end, output_fields: lambda do |object_definitions| @@ -657,11 +819,11 @@ }, get_item_by_id: { - description: "Get item by ID " \ + description: "Get item by ID " \ "in Allocadia", execute: lambda do |_connection, input| - + item = get("/v1/lineitems/#{input['itemId']}") columns = call(:get_line_item_columns_raw, { budgetId: item['budgetId'] }) @@ -675,13 +837,13 @@ item['primaryExternalId'] = externalId end end - + cols = {} columns.each do |col| cols[col['id']] = col end item['cells'] = call(:format_item_cells, { columns: cols, cells: item['cells'] }) - + item['itemId'] = item.delete('id') item.except('_links') end, @@ -713,7 +875,7 @@ end, }, - + get_budget_by_id: { description: "Get budget by ID " \ "in Allocadia", @@ -736,14 +898,14 @@ }, get_all_budgets: { - description: "Get all budgets by folder ID " \ + description: "Get all budgets by folder ID " \ "in Allocadia", execute: lambda do |_connection, input| - budget_filter = input['filter'] ? "?$filter=#{input['filter']}" : "" + budget_filter = input['filter'] ? "?$filter=#{input['filter']}" : "" all_budgets = get("/v1/budgets#{budget_filter}") budgets_in_hierarchy = input['folderId'].blank? ? all_budgets : all_budgets.select {|item| call(:item_in_hierarchy, { root_id: input['folderId'], all_items: all_budgets, item: item }) } - + budgets_in_hierarchy.each do |budget| budget['budgetId'] = budget.delete 'id' budget.delete '_links' @@ -770,18 +932,18 @@ end, }, - + get_line_items_by_budget: { - description: "Get all line items in budget " \ + description: "Get all line items in budget " \ "in Allocadia", execute: lambda do |_connection, input| filter = input['filter'] ? "?$filter=#{input['filter']}" : "" columns = call(:get_line_item_columns_key_id, { budgetId: input['budgetId'] }) budget_items = get("/v1/budgets/#{input['budgetId']}/lineitems#{filter}") - + budget_items&.delete_if { |item| item['parentId'] == nil } # filter out grand total row - + budget_items.each do |item| item['itemId'] = item.delete 'id' item.delete '_links' @@ -814,7 +976,7 @@ input_fields: lambda do |object_definitions| object_definitions['filter'] end, - + output_fields: lambda do |object_definitions| [{ name: 'items', @@ -826,7 +988,7 @@ }, search_line_items: { - description: "Search line items in folder " \ + description: "Search line items in folder " \ "in Allocadia", execute: lambda do |_connection, input| @@ -837,10 +999,10 @@ columns = call(:get_line_item_columns_key_id, { budgetId: input['folderId'] }) all_items = [] - + budgets_in_hierarchy.each do |budget| budget_items = get("/v1/budgets/#{budget['id']}/lineitems#{item_filter}") - + budget_items.each do |item| unless item['parentId'] == nil # filter out grand total row item['itemId'] = item.delete 'id' @@ -874,7 +1036,7 @@ input_fields: lambda do |object_definitions| object_definitions['filter'] end, - + output_fields: lambda do |object_definitions| [{ name: 'items', @@ -887,7 +1049,7 @@ # TODO - this is incomplete - waiting on a "wait" function to be available search_line_items_large_hierarchy: { - description: "Search line items in folder for large hierarchy " \ + description: "Search line items in folder for large hierarchy " \ "in Allocadia", execute: lambda do |_connection, input| @@ -900,9 +1062,9 @@ job = get("/v1/jobs/lineitems/#{job_id}") error(job) end - + all_items = [] - + budget_items.each do |item| unless item['parentId'] == nil # filter out grand total row item['itemId'] = item.delete 'id' @@ -918,7 +1080,7 @@ input_fields: lambda do |object_definitions| object_definitions['filter'] end, - + output_fields: lambda do |object_definitions| [{ name: 'items', @@ -935,17 +1097,17 @@ execute: lambda do |_connection, input| columns = call(:get_line_item_columns_key_name, { budgetId: input.delete('updateBudgetId') }) - + # handle case where it could be a cells object coming in as a string if (input['cells']&.is_a?(String)) input['cells'] = parse_json(input['cells']) end - + input['cells'] = input['cells']&. compact&. map { |key, value| { columns[key]['id'] => { 'value' => call(:format_cell_to_allocadia, { value: value, column: columns[key] } ) } } }&. inject(:merge) - + put("/v1/lineitems" \ "/#{input.delete('itemId')}", input.compact) .after_error_response(/.*/) do |_code, body, _header, message| @@ -973,7 +1135,7 @@ input_fields: lambda do |object_definitions| object_definitions['line_item'].only('itemId','name','cells').required('itemId') end - }, + }, add_line_item: { description: "Add line item" \ @@ -981,17 +1143,17 @@ execute: lambda do |_connection, input| columns = call(:get_line_item_columns_key_name, { budgetId: input['updateBudgetId'] }) - + # handle case where it could be a cells object coming in as a string if (input['cells']&.is_a?(String)) input['cells'] = parse_json(input['cells']) end - + input['cells'] = input['cells']&. compact&. map { |key, value| { columns[key]['id'] => { 'value' => call(:format_cell_to_allocadia, { value: value, column: columns[key] } ) } } }&. inject(:merge) - + post("/v1/budgets/#{input.delete('updateBudgetId')}/lineitems", input.compact) .after_response do |code, body, headers| if code.to_s.match?(/[3-5]\d{2}/) @@ -1022,12 +1184,12 @@ input_fields: lambda do |object_definitions| object_definitions['line_item'].only('name','type','parentId','cells').required('name','type') end, - + output_fields: lambda do |object_definitions| object_definitions['line_item'].only('itemId') end, - }, - + }, + }, pick_lists: { @@ -1040,7 +1202,7 @@ %w[Category CATEGORY], %w[Placeholder PLACEHOLDER]] end, - + column_locations: lambda do |_connection| [ ["ACTUAL","ACTUAL"], From 4bf3aaa318b7d8cf2cd99045b00d50b9b140029d Mon Sep 17 00:00:00 2001 From: Bennett Goh Date: Fri, 2 Aug 2019 11:26:37 +0800 Subject: [PATCH 3/4] hound fixes --- custom_connectors/custom_auth/allocadia.rb | 44 ++++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/custom_connectors/custom_auth/allocadia.rb b/custom_connectors/custom_auth/allocadia.rb index c952fb10..aa091664 100644 --- a/custom_connectors/custom_auth/allocadia.rb +++ b/custom_connectors/custom_auth/allocadia.rb @@ -1151,11 +1151,19 @@ input['cells'] = input['cells']&. compact&. - map { |key, value| { columns[key]['id'] => { 'value' => call(:format_cell_to_allocadia, { value: value, column: columns[key] } ) } } }&. + map { |key, value| + { columns[key]['id'] => + { 'value' => + call(:format_cell_to_allocadia, + value: value, column: columns[key]) + } + } + }&. inject(:merge) - post("/v1/budgets/#{input.delete('updateBudgetId')}/lineitems", input.compact) - .after_response do |code, body, headers| + post("/v1/budgets/#{input.delete('updateBudgetId')}/lineitems", + input.compact) + .after_response do |code, body, headers| if code.to_s.match?(/[3-5]\d{2}/) error("#{code}: #{body}") else @@ -1182,20 +1190,24 @@ }], input_fields: lambda do |object_definitions| - object_definitions['line_item'].only('name','type','parentId','cells').required('name','type') + object_definitions['line_item'] + .only('name', 'type', 'parentId', 'cells').required('name', 'type') end, output_fields: lambda do |object_definitions| object_definitions['line_item'].only('itemId') - end, - }, + end + } }, pick_lists: { - foldersbudgets: ->(_connection) { get('/v1/budgets')&.pluck('name', 'id') || [] }, - budgets: ->(_connection) { get('/v1/budgets?$filter=folder eq false')&.pluck('name', 'id') || [] }, - folders: ->(_connection) { get('/v1/budgets?$filter=folder eq true')&.pluck('name', 'id') || [] }, + foldersbudgets: ->(_connection) { get('/v1/budgets')&. + pluck('name', 'id') || [] }, + budgets: ->(_connection) { get('/v1/budgets?$filter=folder eq false')&. + pluck('name', 'id') || [] }, + folders: ->(_connection) { get('/v1/budgets?$filter=folder eq true')&. + pluck('name', 'id') || [] }, line_item_types: lambda do |_connection| [%w[Line\ item LINE_ITEM], @@ -1205,13 +1217,13 @@ column_locations: lambda do |_connection| [ - ["ACTUAL","ACTUAL"], - ["BUDGET","BUDGET"], - ["DETAILS","DETAILS"], - ["GRID","GRID"], - ["OTHER","OTHER"], - ["PO","PO"], - ["ROLLUP","ROLLUP"] + %w(ACTUAL ACTUAL), + %w(BUDGET BUDGET), + %w(DETAILS DETAILS), + %w(GRID GRID), + %w(OTHER OTHER), + %w(PO PO), + %w(ROLLUP ROLLUP) ] end } From ae4f4414904ab3b04e69b7f6f67762d1dbff12f1 Mon Sep 17 00:00:00 2001 From: Bennett Goh Date: Fri, 2 Aug 2019 11:34:01 +0800 Subject: [PATCH 4/4] hound fix --- custom_connectors/custom_auth/allocadia.rb | 35 ++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/custom_connectors/custom_auth/allocadia.rb b/custom_connectors/custom_auth/allocadia.rb index aa091664..fd81ebf8 100644 --- a/custom_connectors/custom_auth/allocadia.rb +++ b/custom_connectors/custom_auth/allocadia.rb @@ -1156,13 +1156,13 @@ { 'value' => call(:format_cell_to_allocadia, value: value, column: columns[key]) + } } - } }&. inject(:merge) post("/v1/budgets/#{input.delete('updateBudgetId')}/lineitems", - input.compact) + input.compact) .after_response do |code, body, headers| if code.to_s.match?(/[3-5]\d{2}/) error("#{code}: #{body}") @@ -1202,12 +1202,17 @@ }, pick_lists: { - foldersbudgets: ->(_connection) { get('/v1/budgets')&. - pluck('name', 'id') || [] }, - budgets: ->(_connection) { get('/v1/budgets?$filter=folder eq false')&. - pluck('name', 'id') || [] }, - folders: ->(_connection) { get('/v1/budgets?$filter=folder eq true')&. - pluck('name', 'id') || [] }, + foldersbudgets: lambda do |_connection| + get('/v1/budgets')&.pluck('name', 'id') || [] + end, + + budgets: lambda do |_connection| + get('/v1/budgets?$filter=folder eq false')&.pluck('name', 'id') || [] + end, + + folders: lambda do |_connection| + get('/v1/budgets?$filter=folder eq true')&.pluck('name', 'id') || [] + end, line_item_types: lambda do |_connection| [%w[Line\ item LINE_ITEM], @@ -1217,13 +1222,13 @@ column_locations: lambda do |_connection| [ - %w(ACTUAL ACTUAL), - %w(BUDGET BUDGET), - %w(DETAILS DETAILS), - %w(GRID GRID), - %w(OTHER OTHER), - %w(PO PO), - %w(ROLLUP ROLLUP) + %w[ACTUAL ACTUAL], + %w[BUDGET BUDGET], + %w[DETAILS DETAILS], + %w[GRID GRID], + %w[OTHER OTHER], + %w[PO PO], + %w[ROLLUP ROLLUP] ] end }